对于非重载和重载的方法,Scala从"Magnet Pattern“解析隐式转换的方式有很大的不同。
假设有如下实现的特征Apply
(“磁体模式”的变体)。
trait Apply[A] {
def apply(): A
}
object Apply {
implicit def fromLazyVal[A](v: => A): Apply[A] = new Apply[A] {
def apply(): A = v
}
}
现在,我们创建了一个特征Foo
,它有一个带有Apply
实例的apply
,因此我们可以向它传递任意类型A
的任何值,因为存在从A => Apply[A]
的隐式转换。
trait Foo[A] {
def apply(a: Apply[A]): A = a()
}
我们可以使用REPL和this workaround to de-sugar Scala code来确保它像预期的那样工作。
scala> val foo = new Foo[String]{}
foo: Foo[String] = $anon$1@3a248e6a
scala> showCode(reify { foo { "foo" } }.tree)
res9: String =
$line21$read.foo.apply(
$read.INSTANCE.Apply.fromLazyVal("foo")
)
这很有效,但是假设我们将一个复杂的表达式(带有;
)传递给apply
方法。
scala> val foo = new Foo[Int]{}
foo: Foo[Int] = $anon$1@5645b124
scala> var i = 0
i: Int = 0
scala> showCode(reify { foo { i = i + 1; i } }.tree)
res10: String =
$line23$read.foo.apply({
$line24$read.`i_=`($line24$read.i.+(1));
$read.INSTANCE.Apply.fromLazyVal($line24$read.i)
})
正如我们所看到的,隐式转换只应用于复杂表达式的最后一部分(即i
),而不是整个表达式。因此,在我们将i = i + 1
传递给apply
方法时,它的计算是严格的,这并不是我们所期望的。
好消息(或坏消息)。我们可以使scalac
在隐式转换中使用整个表达式。因此,i = i + 1
将像预期的那样缓慢地进行评估。为了做到这一点,我们(出人意料,出人意料!)我们添加了一个重载方法Foo.apply
,它接受任何类型,但不接受Apply
。
trait Foo[A] {
def apply(a: Apply[A]): A = a()
def apply(s: Symbol): Foo[A] = this
}
然后。
scala> var i = 0
i: Int = 0
scala> val foo = new Foo[Int]{}
foo: Foo[Int] = $anon$1@3ff00018
scala> showCode(reify { foo { i = i + 1; i } }.tree)
res11: String =
$line28$read.foo.apply($read.INSTANCE.Apply.fromLazyVal({
$line27$read.`i_=`($line27$read.i.+(1));
$line27$read.i
}))
正如我们所看到的,整个表达式i = i + 1; i
如预期的那样在隐式转换下完成。
所以我的问题是为什么会这样呢?为什么应用隐式转换的作用域取决于类中是否存在重载方法。
发布于 2015-08-18 17:31:59
现在,这是一个棘手的问题。这真的很棒,我不知道“惰性隐式不能覆盖完整块”问题的“解决办法”。谢谢你这么说!
发生的情况与预期类型有关,以及它们如何影响类型推断工作、隐式转换和重载。
类型推断和预期类型
首先,我们必须知道Scala中的类型推断是双向的。大多数推理都是自下而上的(给定a: Int
和b: Int
,推断a + b: Int
),但有些事情是自上而下的。例如,推断lambda的参数类型是自上而下的:
def foo(f: Int => Int): Int = f(42)
foo(x => x + 1)
在第二行中,在将foo
解析为def foo(f: Int => Int): Int
之后,类型推断器可以判断x
必须是Int
类型。在对lambda本身进行类型检查之前,它会这样做。它将类型信息从函数应用程序向下传播到lambda,这是一个参数。
自上而下的推理基本上依赖于预期类型的概念。当类型检查程序的AST的节点时,类型检查器不会空手启动。它从“上面”(在本例中是函数应用程序节点)接收一个期望的类型。当在上面的例子中检查λx => x + 1
的类型时,期望的类型是Int => Int
,因为我们知道foo
期望的参数类型。这将驱动类型推断来推断参数x
的Int
,从而允许对x + 1
进行类型检查。
预期的类型向下传播某些构造,例如,块({}
)以及if
和match
es的分支。因此,您还可以使用以下命令调用foo
foo({
val y = 1
x => x + y
})
而且类型检查器仍然能够推断出x: Int
。这是因为,当检查块{ ... }
的类型时,期望的类型Int => Int
将传递给最后一个表达式的类型检查,即x => x + y
。
隐式转换和预期类型
现在,我们必须在混合中引入隐式转换。当类型检查一个节点产生一个类型为T
的值,但该节点的预期类型是U
,其中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)
提供:
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]
或Symbol
的隐式转换。它找到fromLazyVal[Int]
,并将其应用于整个参数。它不再将其推入块中,给出:
fromLazyVal({
i = i + 1
i
}): Apply[Int]
在这种情况下,整个块都是惰性的。
解释到此结束。总而言之,主要的区别是在类型检查块时是否存在预期的类型。对于预期的类型,隐式转换被尽可能地下推,下推到只有i
。如果没有预期的类型,隐式转换将后验地应用于整个参数,即整个块。
https://stackoverflow.com/questions/32064375
复制相似问题