对于非重载和重载方法,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
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们创建一个Foo具有单个apply实例的特征,Apply因此我们可以将任意类型的值传递给它,A因为存在隐式转换A => Apply[A].
trait Foo[A] {
def apply(a: Apply[A]): A = a()
}
Run Code Online (Sandbox Code Playgroud)
我们可以确保它使用REPL按预期工作,并使用此解决方法来减少Scala代码.
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")
)
Run Code Online (Sandbox Code Playgroud)
这很好用,但假设我们将一个复杂的表达式(with ;)传递给该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)
})
Run Code Online (Sandbox Code Playgroud)
我们可以看到,隐式转换仅应用于复杂表达式的最后部分(即i),而不是整个表达式.因此,i = i + 1在我们将其传递给apply方法的时刻进行了严格评估,这不是我们所期望的.
好消息(或坏消息).我们可以scalac在隐式转换中使用整个表达式.所以i = i + 1将按照预期懒洋洋地进行评估.要做到这一点,我们(surprize,surprize!)我们添加一个Foo.apply任何类型的重载方法,但不是Apply.
trait Foo[A] {
def apply(a: Apply[A]): A = a()
def apply(s: Symbol): Foo[A] = this
}
Run Code Online (Sandbox Code Playgroud)
然后.
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
}))
Run Code Online (Sandbox Code Playgroud)
正如我们所看到的,整个表达式i = i + 1; i使其在预期的隐式转换下进行.
所以我的问题是为什么呢?为什么应用隐式转换的范围取决于类中是否存在重载方法的事实.
sjr*_*jrd 17
现在,这是一个棘手的问题.它实际上非常棒,我不知道"懒惰隐含不会覆盖整个块"问题的"解决方法".感谢那!
所发生的事情与预期类型有关,它们如何影响类型推断工作,隐式转换和重载.
首先,我们必须知道Scala中的类型推断是双向的.大多数推理都是自下而上(给定a: Int和b: Int推断a + b: Int),但有些事情是自上而下的.例如,推断lambda的参数类型是自上而下的:
def foo(f: Int => Int): Int = f(42)
foo(x => x + 1)
Run Code Online (Sandbox Code Playgroud)
在第二行,解决后foo要def foo(f: Int => Int): Int,类型inferencer可以告诉大家,x必须是类型的Int.它是在对lambda本身进行类型检查之前这样做的.它将类型信息从函数应用程序传播到lambda,这是一个参数.
自上而下的推断基本上依赖于预期类型的概念.当对节目的AST节点进行类型检查时,类型检查器不会空手而归.它从"上方"(在这种情况下,函数应用程序节点)接收期望的类型.当x => x + 1在上面的例子中对lambda 进行类型检查时Int => Int,预期的类型是,因为我们知道期望的参数类型foo.这会将类型推断驱动为推断Int参数x,从而允许进行类型检查x + 1.
预期类型沿某些构造传播,例如,块({})和ifs和matches 的分支.因此,你也可以打电话foo给
foo({
val y = 1
x => x + y
})
Run Code Online (Sandbox Code Playgroud)
并且typechecker仍然可以推断x: Int.这是因为,当对块{ ... }进行类型检查时,期望的类型Int => Int被传递给最后一个表达式的类型检查,即x => x + y.
现在,我们必须将隐式转换引入混合中.当类型检查的节点产生类型的值T,但该节点的预期类型是U哪里T <: U是假的,typechecker寻找一个隐含的T => U(我这里大概简化事情有点,但关键仍是如此).这就是你的第一个例子不起作用的原因.让我们仔细看看:
trait Foo[A] {
def apply(a: Apply[A]): A = a()
}
val foo = new Foo[Int] {}
foo({
i = i + 1
i
})
Run Code Online (Sandbox Code Playgroud)
调用时foo.apply,参数(即块)的预期类型是Apply[Int](A已经实例化Int).我们可以像这样"写"这个类型检查"状态":
{
i = i + 1
i
}: Apply[Int]
Run Code Online (Sandbox Code Playgroud)
这个期望的类型传递给块的最后一个表达式,它给出:
{
i = i + 1
(i: Apply[Int])
}
Run Code Online (Sandbox Code Playgroud)
此时,由于i: Int和预期的类型是Apply[Int],typechecker发现了隐式转换:
{
i = i + 1
fromLazyVal[Int](i)
}
Run Code Online (Sandbox Code Playgroud)
这导致只是i被夷为平地.
好的,有时间在那里抛出超载!当类型检查器看到一个重载方法的应用时,决定一个预期的类型会有更多的麻烦.我们可以通过以下示例看到:
object Foo {
def apply(f: Int => Int): Int = f(42)
def apply(f: String => String): String = f("hello")
}
Foo(x => x + 1)
Run Code Online (Sandbox Code Playgroud)
得到:
error: missing parameter type
Foo(x => x + 1)
^
Run Code Online (Sandbox Code Playgroud)
在这种情况下,类型检查器未能找出期望的类型会导致不推断参数类型.
如果我们对您的问题采取"解决方案",我们会产生不同的后果:
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
})
Run Code Online (Sandbox Code Playgroud)
现在,在对块进行类型检查时,类型检查器没有预期的类型.因此,它将在没有表达式的情况下检查最后一个表达式,并最终将整个块进行类型检查Int:
{
i = i + 1
i
}: Int
Run Code Online (Sandbox Code Playgroud)
只有现在,已经有一个已经过类固定的参数,它是否会尝试解决重载问题.由于没有任何重载直接符合,因此它会尝试将隐式转换Int应用于Apply[Int]或者Symbol.它发现fromLazyVal[Int],它适用于整个论证.它不再将它推入块内,给出:
fromLazyVal({
i = i + 1
i
}): Apply[Int]
Run Code Online (Sandbox Code Playgroud)
在这种情况下,整个块是lazified.
这结束了解释.总而言之,主要区别在于对块进行类型检查时是否存在预期类型.对于期望的类型,隐式转换被尽可能地向下推,直到只有i.如果没有预期的类型,隐式转换将在整个参数(即整个块)上进行后验.