没有路径的路径依赖类型?

n. *_* m. 5 scala path-dependent-type dependent-type

考虑这个简单的例子:

class Outer {
  case class Inner()
  def test(i: Inner) = {}
}
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,由于类型不匹配,这不会编译:

val o1 = new Outer()
val o2 = new Outer()

o1.test(o2.Inner())  // doesn't compile
Run Code Online (Sandbox Code Playgroud)

如果我们想定义一个独立的函数怎么办?

这不好

def test[X <: Outer](a: X#Inner, b: X#Inner) = {}
Run Code Online (Sandbox Code Playgroud)

因为这样编译就好像一切正​​常

test(o1.Inner(), o2.Inner())
Run Code Online (Sandbox Code Playgroud)

这有效

def test(x: Outer)(a: x.Inner, b: x.Inner) = {}
Run Code Online (Sandbox Code Playgroud)

因为这编译:

test(o1)(o1.Inner(), o1.Inner())
Run Code Online (Sandbox Code Playgroud)

而这不会:

test(o1)(o1.Inner(), o2.Inner())
Run Code Online (Sandbox Code Playgroud)

但是,我们必须向 传递一个额外的Outer参数test。有没有可能避免这种情况?理想情况下,以下应该起作用:

test(o1.Inner(), o1.Inner()) // ok
test(o1.Inner(), o2.Inner()) // compilation error
Run Code Online (Sandbox Code Playgroud)

Jas*_*r-M 5

我认为,开箱即用,不可能以令人满意的方式执行它。例如,一种可能的解决方案可能是:

scala> def test[X <: Outer#Inner](a: X)(b: X) = ()
test: [X <: Outer#Inner](a: X)(b: X)Unit

scala> test(o1.Inner())(o1.Inner())

scala> test(o1.Inner())(o2.Inner())
<console>:16: error: type mismatch;
 found   : o2.Inner
 required: o1.Inner
       test(o1.Inner())(o2.Inner())
                                ^
Run Code Online (Sandbox Code Playgroud)

看起来不错,但您可以通过显式传入类型参数来规避它。(顺便说一下,@OlivierBlanvillain 的解决方案也是如此)

scala> test[Outer#Inner](o1.Inner())(o2.Inner())
Run Code Online (Sandbox Code Playgroud)

现在让我们尝试以下操作:

scala> def test[X <: Outer](a: X#Inner)(b: X#Inner) = ()
test: [X <: Outer](a: X#Inner)(b: X#Inner)Unit

scala> test(o1.Inner())(o2.Inner())
Run Code Online (Sandbox Code Playgroud)

Doesn't work, scalac infers X to be Outer, which isn't specific enough, and we could supply Outer as explicit type argument anyway. We need a way to force X to be a singleton type so that it can only represent the path o1 or o2, and not some general type that can be inhabited by an infinite amount of values. There is a way. Scala has the marker trait Singleton for this purpose. Let's try it:

scala> def test[X <: Outer with Singleton](a: X#Inner)(b: X#Inner) = ()
test: [X <: Outer with Singleton](a: X#Inner)(b: X#Inner)Unit

scala> test(o1.Inner())(o1.Inner())
<console>:15: error: inferred type arguments [Outer] do not conform to method test's type parameter bounds [X <: Outer with Singleton]
       test(o1.Inner())(o1.Inner())
       ^
<console>:15: error: type mismatch;
 found   : o1.Inner
 required: X#Inner
       test(o1.Inner())(o1.Inner())
                    ^
Run Code Online (Sandbox Code Playgroud)

Now our valid case doesn't work anymore! The problem is that scalac refuses to infer singleton types. We have to pass them in explicitly:

scala> test[o1.type](o1.Inner())(o1.Inner())
Run Code Online (Sandbox Code Playgroud)

The invalid cases don't work anymore:

scala> test(o1.Inner())(o2.Inner())
<console>:16: error: inferred type arguments [Outer] do not conform to method test's type parameter bounds [X <: Outer with Singleton]
       test(o1.Inner())(o2.Inner())
       ^
<console>:16: error: type mismatch;
 found   : o1.Inner
 required: X#Inner
       test(o1.Inner())(o2.Inner())
                    ^

scala> test[o1.type](o1.Inner())(o2.Inner())
<console>:16: error: type mismatch;
 found   : o2.Inner
 required: o1.Inner
       test[o1.type](o1.Inner())(o2.Inner())
                                         ^

scala> test[Outer](o1.Inner())(o2.Inner())
<console>:17: error: type arguments [Outer] do not conform to method test's type parameter bounds [X <: Outer with Singleton]
       test[Outer](o1.Inner())(o2.Inner())
           ^
Run Code Online (Sandbox Code Playgroud)

So this enforces the rules we want, but you have to pass in the types explicitly...


EDIT

Actually it turns out you can enforce this without losing type inference and without help from any external libraries, but you're probably not going to like it :-p

META EDIT as pointed out in the comments this can still be circumvented if you try hard enough, so I guess you're stuck with the above solution.

scala> import scala.language.existentials
import scala.language.existentials

scala> def test[X <: x.Inner forSome { val x: Outer }](a: X, b: X) = ()
test: [X <: x.Inner forSome { val x: Outer }](a: X, b: X)Unit

scala> test(o1.Inner(), o1.Inner())

scala> test(o1.Inner(), o2.Inner())
<console>:16: error: inferred type arguments [Outer#Inner] do not conform to method test's type parameter bounds [X <: x.Inner forSome { val x: Outer }]
       test(o1.Inner(), o2.Inner())
       ^
<console>:16: error: type mismatch;
 found   : o1.Inner
 required: X
       test(o1.Inner(), o2.Inner())
                    ^
<console>:16: error: type mismatch;
 found   : o2.Inner
 required: X
       test(o1.Inner(), o2.Inner())
                                ^

scala> test[o1.Inner](o1.Inner(), o2.Inner())
<console>:16: error: type mismatch;
 found   : o2.Inner
 required: o1.Inner
       test[o1.Inner](o1.Inner(), o2.Inner())
                                          ^
Run Code Online (Sandbox Code Playgroud)