Given a tuple with elements of type A
and another type parametrised in A
:
trait Writer[-A] { def write(a: A): Unit }
case class Wri
Running with -Xprint:typer
is illuminating here. The problem with test2
is
that there is an existential type, which appears in two separate places: both
write.value
and write.writer
have an existential type, but, crucially, the
compiler has no way of knowing that they have the same existentially
quantified type variable. Even though you access them from the same object, the
compiler forgets they came from the same place.
When test1
is fully typed, you see:
def test1(set: Set[Write[_]], cache: Cache) =
set.foreach(((x0$1: Write[_]) => x0$1 match {
case (value: _$1, writer: Writer[_$1])Write[_$1]((value @ _), (writer @ _)) =>
cache.store[_$1](value, writer)
}));
The type variable _$1
is matched along with the values. Matching the type variable _$1
binds it in
the scope of the case
, so it's not existential anymore, and Scala can tell
that value
and writer
have the same type parameter.
The solution for test2
is to not use existentials:
def test2[A]( set: Set[ Write[ A ]], cache: Cache ) {
set.foreach { write =>
cache.store( write.value, write.writer )
}
}
or to bind the type variable with a match:
def test2( set: Set[ Write[ _ ]], cache: Cache ) {
set.foreach { case write: Write[a] =>
cache.store( write.value, write.writer )
}
}
edit
Let me endeavor to answer the new questions you brought up.
The reason test3
does not work, is that when you write:
set.foreach( process )
process
, which is polymorphic, has to be made monomorphic, for two reasons (that I know of):
Functions in Scala cannot be polymorphic; only methods can be. process
as defined as a method; when used as a first-class function, it is a function.
The way the compiler does type inference is mostly by taking polymorphic values and unifying them together to make them less polymorphic. Passing an actual polymorphic value as a method argument would require higher-rank types.
The reason that test4
does work is that the function literal:
set.foreach( w => process( w ))
is actually not a polymorphic function! It takes as its argument an exestentially qualified type; but not a polymorphic type. It then calls the method (not the function) process
, and matches the existential type variable to process
's type parameter. Pretty wild, eh?
You could also write:
set.foreach( process(_) )
which, creating an anonymous function, means the same thing.
Another route you may or may not find appropriate would be to discard existential types and use type members:
trait Writable {
type A
val value: A
val writer: Writer[A]
}
case class Write[T]( value: T, writer: Writer[ T ]) extends Writable {
type A = T
}
def test2( set: Set[Writable], cache: Cache ) {
set.foreach { write =>
cache.store( write.value, write.writer )
}
}
Here Scala is able to see that write.value
and write.writer
have the same
type parameter because they have the same path dependent type.