现在,这是一个棘手的问题。它实际上非常棒,我不知道“惰性隐式不覆盖完整块”问题的“解决方法”。感谢那!
发生的事情与预期类型,以及它们如何影响类型推断工作、隐式转换和重载。
类型推断和预期类型
首先,我们必须知道 Scala 中的类型推断是双向的。大多数推理都是自下而上进行的(给定a: Int
and b: Int
, infer a + b: Int
),但有些事情是自上而下的。例如,推断 lambda 的参数类型是自上而下的:
def foo(f: Int => Int): Int = f(42)
foo(x => x + 1)
在第二行中,解决后foo
to be def foo(f: Int => Int): Int
,类型推断器可以知道x
必须是类型Int
。确实如此before对 lambda 本身进行类型检查。它将类型信息从函数应用程序向下传播到 lambda(参数)。
自上而下的推理基本上依赖于以下概念预期类型。当对程序的 AST 节点进行类型检查时,类型检查器不会空手启动。它从“上方”(在本例中为函数应用程序节点)接收预期类型。对 lambda 进行类型检查时x => x + 1
在上面的例子中,预期的类型是Int => Int
,因为我们知道期望的参数类型foo
。这将类型推断驱动为推断Int
对于参数x
,这又允许类型检查x + 1
.
预期的类型会沿着某些构造传播,例如块({}
)和分支if
s and match
es.因此,您也可以调用foo
with
foo({
val y = 1
x => x + y
})
并且类型检查器仍然能够推断x: Int
。那是因为,在类型检查块时{ ... }
, 预期类型Int => Int
被传递到最后一个表达式的类型检查,即x => x + y
.
隐式转换和预期类型
现在,我们必须将隐式转换引入其中。当类型检查节点产生类型值时T
,但该节点的预期类型是U
where T <: U
为 false,类型检查器会查找隐式T => U
(我可能在这里稍微简化了一些事情,但要点仍然是正确的)。这就是为什么你的第一个例子不起作用。让我们仔细看看:
trait Foo[A] {
def apply(a: Apply[A]): A = a()
}
val foo = new Foo[Int] {}
foo({
i = i + 1
i
})
打电话时foo.apply
,参数(即块)的预期类型是Apply[Int]
(A
已经被实例化为Int
)。我们可以像这样“写”这个类型检查器“状态”:
{
i = i + 1
i
}: Apply[Int]
这个预期的类型是传承下来到块的最后一个表达式,它给出:
{
i = i + 1
(i: Apply[Int])
}
至此,自从i: Int
预期类型是Apply[Int]
,类型检查器找到隐式转换:
{
i = i + 1
fromLazyVal[Int](i)
}
这只会导致i
变得懒惰。
重载和预期类型
好啦,是时候投入重载了!当类型检查器看到重载方法的应用时,它在决定预期类型时会遇到更多困难。我们可以通过以下示例看到这一点:
object Foo {
def apply(f: Int => Int): Int = f(42)
def apply(f: String => String): String = f("hello")
}
Foo(x => x + 1)
gives:
error: missing parameter type
Foo(x => x + 1)
^
在这种情况下,类型检查器无法找出预期类型会导致无法推断参数类型。
如果我们采用您的“解决方案”来解决您的问题,我们会得到不同的结果:
trait Foo[A] {
def apply(a: Apply[A]): A = a()
def apply(s: Symbol): Foo[A] = this
}
val foo = new Foo[Int] {}
foo({
i = i + 1
i
})
现在,当对块进行类型检查时,类型检查器具有没有预期的类型跟...共事。因此,它将对最后一个不带表达式的表达式进行类型检查,并最终将整个块作为一个Int
:
{
i = i + 1
i
}: Int
只有现在,通过已经进行类型检查的参数,它才会尝试解决重载。由于没有一个重载直接符合,它尝试应用隐式转换Int
到任一Apply[Int]
or Symbol
。它发现fromLazyVal[Int]
,它适用于到整个论证。它不再将其推入块内,给出:
fromLazyVal({
i = i + 1
i
}): Apply[Int]
在这种情况下,整个块都是惰性的。
解释到此结束。总而言之,主要区别在于对块进行类型检查时是否存在预期类型。对于预期的类型,隐式转换会尽可能向下推,直到i
。如果没有预期的类型,则隐式转换将后验应用于整个参数(即整个块)。