我一直认为上下文边界和隐式参数列表的行为完全相同,但显然不同。
在下面的示例中,我期望summon1[Int]
和summon2[Int]
返回相同的类型,但它们不会。我期望summon2[Int]
返回依赖于路径的类型,但是相反,它给了我类型投影。为什么?
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!
此外,在斯卡拉2.13,你可以使用materializer(非特异性的更一般的形式Foo
)恢复单身类型(像它做在斯卡拉3)
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
,则不能f.B
在返回类型中显式引用该类型。而且,如果你未明确指定返回类型,我们会f.B
因为缺乏稳定的前缀而无法推断出这种类型f
(另请参见Aux-pattern用法编译而不会推断出适当的类型)。
啊,我明白了。换句话说,
summon1
之所以起作用是因为通过f
句柄summon2
有指向类型的路径,但是没有。知道了👍@gogstad注意,通常(当然也有例外)具有隐式的值和方法而没有显式的返回类型被认为是不好的做法。具有显式的返回类型可以帮助推断并避免引入带有更改的错误。