关于路径相关类型的上下文边界和隐式参数列表的行为

Behaviour of context bounds and implicit parameter lists with regards to path dependent types

我一直认为上下文边界和隐式参数列表的行为完全相同,但显然不是。

在下面的示例中,我希望 summon1[Int]summon2[Int] 到 return 是同一类型,但它们不是。我希望 summon2[Int] 到 return 是路径相关类型,但它给了我一个类型投影。为什么?

Welcome to the Ammonite Repl 2.2.0 (Scala 2.13.3 Java 11.0.2)
@ trait Foo[A] {
    type B
    def value: B
  }
defined trait Foo

@ implicit def fooInt = new Foo[Int] {
      override type B = String
      override def value = "Hey!"
    }
defined function fooInt

@ implicit def fooString = new Foo[String] {
      override type B = Boolean
      override def value = true
    }
defined function fooString

@ def summon1[T](implicit f: Foo[T]) = f.value
defined function summon1

@ def summon2[T: Foo] = implicitly[Foo[T]].value
defined function summon2

@ summon1[Int]
res5: String = "Hey!"

@ summon2[Int]
res6: Foo[Int]#B = "Hey!"

@

问题主要不在于上下文绑定与隐式参数的区别(应该没有任何区别 (*)),问题在于implicitly 可以打破隐式发现的类型

https://typelevel.org/blog/2014/01/18/implicitly_existential.html

如果您使用自定义实体化器修复 summon2,这将按预期工作

def materializeFoo[T](implicit f: Foo[T]): Foo[T] { type B = f.B } = f

def summon2[T: Foo] = materializeFoo[T].value

summon2[Int]
// val res: String = Hey!

有趣的是 shapeless.the 没有帮助

def summon2[T: Foo] = the[Foo[T]].value

summon2[Int]
// val res: Foo[Int]#B = Hey!

同样在 Scala 2.13 中,您可以使用更通用的实体化器形式(不特定于 Foo)returning 单例类型(就像 Scala 3 中的 done

def materialize[A](implicit f: A): f.type = f

def summon2[T: Foo] = materialize[Foo[T]].value

val y = summon2[Int]
// val res: String = Hey!

(*) 嗯,有一个区别,如果不引入参数名 f 就不能在 return 类型中显式引用类型 f.B .如果你没有明确指定 return 类型,正如我们所看到的那样,由于缺少 stable prefix f (see also ).

,无法推断出这种类型 f.B