Given a tuple with elements of type A and another type parametrised in A:
trait Writer[-A] { def write(a: A): Unit }
case class Write[A](value: A, writer: Writer[A])
And a use site:
trait Cache { def store[A](value: A, writer: Writer[A]): Unit }
Why does the following work as expected, using the tuple’s extractor:
def test1(set: Set[Write[_]], cache: Cache): Unit =
set.foreach {
case Write(value, writer) => cache.store(value, writer)
}
But the following fails:
def test2(set: Set[Write[_]], cache: Cache ): Unit =
set.foreach { write =>
cache.store(write.value, write.writer)
}
with error message
found : Writer[_$1] where type _$1
required: Writer[Any]
cache.store(write.value, write.writer)
^
Can I fix the second form (test2) to compile properly?
EDIT
Departing from the ideas by Owen I tried out if I can make it work without pattern matching at all (which is what I wanted in the first place). Here are two more strange cases, one working, the other not:
// does not work
def test3(set: Set[Write[_]], cache: Cache): Unit = {
def process[A](write: Write[A]): Unit =
cache.store(write.value, write.writer)
set.foreach(process)
}
// _does work_
def test4(set: Set[Write[_]], cache: Cache): Unit = {
def process[A](write: Write[A]): Unit =
cache.store(write.value, write.writer)
set.foreach(w => process(w))
}
Still pretty obscure to me…
Running with
-Xprint:typeris illuminating here. The problem withtest2isthat there is an existential type, which appears in two separate places: both
write.valueandwrite.writerhave an existential type, but, crucially, thecompiler 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
test1is fully typed, you see:The type variable
_$1is matched along with the values. Matching the type variable_$1binds it inthe scope of the
case, so it’s not existential anymore, and Scala can tellthat
valueandwriterhave the same type parameter.The solution for
test2is to not use existentials:or to bind the type variable with a match:
edit
Let me endeavor to answer the new questions you brought up.
The reason
test3does 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.
processas 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
test4does work is that the function literal: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 toprocess‘s type parameter. Pretty wild, eh?You could also write:
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:
Here Scala is able to see that
write.valueandwrite.writerhave the sametype parameter because they have the same path dependent type.