Warm tip: This article is reproduced from serverfault.com, please click

scala-关于路径依赖类型的上下文范围和隐式参数列表的行为

(scala - Behaviour of context bounds and implicit parameter lists with regards to path dependent types)

发布于 2020-11-29 15:53:55

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

在下面的示例中,我期望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!"

@
Questioner
gogstad
Viewed
11
Dmytro Mitin 2020-11-30 02:04:41

事情主要不是上下文绑定与隐式参数的区别不应有任何区别(*)),而是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用法编译而不会推断出适当的类型)。