Given:
scala> sealed trait Parent
defined trait Parent
scala> case object Boy extends Parent
defined object Boy
scala> case object Girl extends Parent
defined object Girl
scala> trait F {
| type A
| def x: A
| }
defined trait F
scala> case object FImpl extends F {
| override type A = Parent
| def x: Parent = Boy
| }
defined object FImpl
I then defined a method:
scala> def foobar(f: F)(matcher: f.A => Boolean): Boolean =
| matcher(f.x)
foobar: (f: F)(matcher: f.A => Boolean)Boolean
scala> foobar(FImpl)(_ match { case Boy => true; case Girl => false})
res3: Boolean = true
I'm confused as to how this works. The compiler must know fA
's type at compile-time then?
Your question is basically: How can the compiler see the member fA
when f
is a runtime value? The answer is that it doesn't. Syntactically, fA
looks like it is accessing a member of f
, but in fact it is relying only on the type of f
.
When you write:
object FImpl extends F {
override type A = Parent
def x: Parent = Boy
}
FImpl
defines a new singleton type, known as FImpl.type
. So, FImpl.type#A
is Parent
.
When you call foobar
, f.type
is identified with FImpl.type
, so fA
aka f.type#A
is FImpl.type#A
aka Parent
.
Consider these two examples:
def needsParent(p: Parent) = ???
def foobar(f: F)(matcher: f.A => Boolean) = ???
foobar(FImpl)(needsParent) // Works
foobar(FImpl: F)(needsParent) // Does not work
Although the runtime values are the same, the types differ, and so the compiler accepts one and rejects the other.
In other words, Scala's dependent types are a clever fiction -- in fact, types never depend on actual runtime values. But it turns out that just by identifying types with other types, it becomes possible to keep track of values to a limited extent, giving the impression of types that depend on values.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.