My favorite example:
case class Board(length: Int, height: Int) {
case class Coordinate(x: Int, y: Int) {
require(0 <= x && x < length && 0 <= y && y < height)
}
val occupied = scala.collection.mutable.Set[Coordinate]()
}
val b1 = Board(20, 20)
val b2 = Board(30, 30)
val c1 = b1.Coordinate(15, 15)
val c2 = b2.Coordinate(25, 25)
b1.occupied += c1
b2.occupied += c2
// Next line doesn't compile
b1.occupied += c2
So, the type of Coordinate
is dependent on the instance of Board
from which it was instantiated. There are all sort of things that can be accomplished with this, giving a sort of type safety that is dependent on values and not types alone.
This might sound like dependent types, but it is more limited. For example, the type of occupied
is dependent on the value of Board
. Above, the last line doesn't work because the type of c2
is b2.Coordinate
, while occupied
's type is Set[b1.Coordinate]
. Note that one can use another identifier with the same type of b1
, so it is not the identifier b1
that is associated with the type. For example, the following works:
val b3: b1.type = b1
val c3 = b3.Coordinate(10, 10)
b1.occupied += c3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With