Path-Dependent type inside class value in Scala

2020-06-06 06:07发布

问题:

I would like to give a value of a type with an abstract type to a class and later use it's path dependent type. Look at the following example (using Scala 2.10.1):

trait Foo {
  type A
  def makeA: A
  def useA(a: A): Unit
}

object Test {

  class IntFoo extends Foo {
    type A = Int
    def makeA = 1
    def useA(a: Int) = println(a)
  }

  class FooWrap(val a: Foo) {
    def wrapUse(v: a.A) = a.useA(v)
  }

  val foo = new IntFoo

  /* Path dependent locally */
  val bar = foo  
  bar.useA(foo.makeA)   // works

  /* Path dependent through class value */
  val fooWrap = new FooWrap(foo)

  fooWrap.a.useA(foo.makeA)  // fails
  // error: type mismatch; found : Int required: Test.fooWrap.a.A

  fooWrap.wrapUse(foo.makeA) // fails
  // error: type mismatch; found : Int required: Test.fooWrap.a.A

}

First, I do not understand the fundamental difference between the local and the class-value case (note the public, immutable value) and why the type checking fails (because obviously Test.fooWrap.a.A =:= foo.A). Is this a limitation of the Scala compiler?

Second, how can I achieve what I am trying to do?

UPDATE

It seems that this can be achieved by using generics and inline type-constraints:

class FooWrap[T](val a: Foo { type A = T }) {
  def wrapUse(v: T) = a.useA(v)
}

However, in my case, A is actually a higher-kinded type, so the example becomes:

trait Foo {
  type A[T]
  def makeA[T]: A[T]
  def useA(a: A[_]): Unit
}

object Test {

  class OptFoo extends Foo {
    type A[T] = Option[T]
    def makeA[T] = None
    def useA(a: A[_]) = println(a.get)
  }

  class FooWrap(val a: Foo) {
    def wrapUse(v: a.A[_]) = a.useA(v)
  }

  val foo = new OptFoo

  /* Path dependent locally (snip) */

  /* Path dependent through class value */
  val fooWrap = new FooWrap(foo)

  fooWrap.a.useA(foo.makeA)  // fails
  // polymorphic expression cannot be instantiated to expected type;
  // found : [T]None.type required: Test.fooWrap.a.A[_]

  fooWrap.wrapUse(foo.makeA) // fails
  // polymorphic expression cannot be instantiated to expected type;
  // found : [T]None.type required: Test.fooWrap.a.A[_]

}

回答1:

In your original question, your problem is that the Scala compiler is unable to prove equality of the result type of foo.makeA with the argument type of fooWrap.a.useA. To do that it would need to be able to prove the identity of foo with fooWrap.a which we can intuitively see must be the case here, but which isn't straightforward for the compiler to track.

There are a couple of ways to work around this problem. First, you could use fooWrap.a uniformly in place of foo,

scala> fooWrap.a.useA(fooWrap.a.makeA)
1

Now it's simple for the compiler to recognize the prefix of A (fooWrap.a) as being the same in both occurrences.

Second, you could parametrize FooWrap in a way which captures the type of its Foo argument more precisely,

scala> class FooWrap[F <: Foo](val a: F) {
     |   def wrapUse(v: a.A) = a.useA(v)
     | }
defined class FooWrap

scala> val fooWrap = new FooWrap(foo)
fooWrap: FooWrap[IntFoo] = FooWrap@6d935671

scala> fooWrap.a.useA(foo.makeA)
1

Here the type argument of FooWrap is inferred as IntFoo rather than as bare Foo, hence A is known to be exactly Int, as it is in the result type of foo.makeA.

In your update you introduce an additional wrinkle: you change the signature of useA to,

def useA(a: A[_]): Unit

The _ here is an existential which will frustrate all attempts to coax the compiler into proving useful type equalities. Instead you need something along the lines of,

trait Foo {
  type A[T]
  def makeA[T]: A[T]
  def useA[T](a: A[T]): Unit
}

class OptFoo extends Foo {
  type A[T] = Option[T]
  def makeA[T]: A[T] = None
  def useA[T](a: A[T]) = a map println
}

class FooWrap[F <: Foo](val a: F) {
  def wrapUse[T](v: a.A[T]) = a.useA(v)
}

val foo = new OptFoo

Sample REPL session,

scala> val fooWrap = new FooWrap(foo)
fooWrap: FooWrap[OptFoo] = FooWrap@fcc10a7

scala> fooWrap.a.useA(foo.makeA)

scala>


回答2:

The higher kinded-type can be added to FooWrap as generic parameter, too:

class FooWrap[T[V]](val a: Foo { type A[V] = T[V] }) {
  def wrapUse(v: T[_]) = a.useA(v)
}

but (in this example) the inference fails:

val fooWrap = new FooWrap[Option](foo)

Otherwise:

- type mismatch; found : Test.foo.type (with underlying type Test.OptFoo) required: Foo{type A[V] = T[V]}
- inferred kinds of the type arguments (Option[V]) do not conform to the expected kinds of the type parameters (type T) in class FooWrap. Option[V]'s type parameters do not match type T's expected 
 parameters: class Option has one type parameter, but type T has one

Any other, nicer solutions?