Strange type mismatch when using member access instead of extractor

前端 未结 1 1687
一个人的身影
一个人的身影 2021-01-18 12:31

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         


        
1条回答
  •  再見小時候
    2021-01-18 12:40

    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):

    1. 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.

    2. 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.

    0 讨论(0)
提交回复
热议问题