在下面的代码中,在println(2)
行周围应用隐式转换;我曾愚蠢地期望它适用于整个块{ println(1); println(2) }
。我应该如何推断编译器将隐式放在哪里?
object Executor {
private var runnable: Runnable = _
def setRunnable(runnable: Runnable) {
this.runnable = runnable
}
def execute() { runnable.run() }
}
object Run extends App {
implicit def blockToRunnable(p: ⇒ Any): Runnable =
new Runnable { def run() = p }
Executor.setRunnable {
println(1)
println(2)
}
println("Before execute")
Executor.execute()
}
我将这种行为合理化如下:根据规范,块{s1; s2; ...; sn; e }
的类型是最后一个表达式e
的类型。
编译器将e
与Runnable
进行类型检查。这失败了,所以它搜索将e
转换为Runnable
的隐式转换。它应该是这样的:
{ s1; s2; ... sn; convert(e) }
这个小例子用scala -Xprint:typer
证实了这一点:
class A
implicit def convert(a: A): String = a.toString
def f(s: String) { println(s) }
f{ println(1); new A }
打印:
private[this] val res0: Unit = $line3.$read.$iw.$iw.f({
scala.this.Predef.println(1);
$line2.$read.$iw.$iw.convert(new $line1.$read.$iw.$iw.A())
});
根据规范,当表达式的类型与期望的类型不匹配时,应用隐式转换。关键的观察是,当输入块时,期望的类型是如何线程化的。
如果表达式e
的类型为T
,且T不符合表达式期望的类型pt
,则。在本例中,搜索适用于e且其结果类型符合pt的隐式v。
在6.11 Blocks小节中,块最后表达式的预期类型定义为
根据这个规范,编译器似乎有这样的行为。块的期望类型是最终表达式
e
的期望类型是块的期望类型。
Runnable
, println(2)
的期望类型也变成了Runnable
。
一个建议:如果你想知道应用了什么隐式,你可以使用Eclipse的Scala IDE 2.1的夜间构建。它可以'highlight implicit '。
编辑:我承认在作用域中隐式调用名字是令人惊讶的。
问题是你把块当成了一堆东西,当成了一段代码。他们不是。{ a; b; c }
是而不是一段可以传递的代码。
那么,你应该如何推断隐含式呢?实际上,您应该如何推断视图,它们是隐式转换。视图应用于需要更改的值。在您的示例中,
的值{
println(1)
println(2)
}
正在传递给setRunnable
。块的值是它的最后一个表达式的值,因此它将println(2)
的结果传递给setRunnable
。因为是Unit
,而setRunnable
需要一个Runnable
,所以搜索并找到了一个隐式,所以println(2)
被传递给了严重错误命名的blockToRunnable
。
最重要的是,这是我已经在Stack Overflow上给过很多次的建议(很多人都试图做同样的事情),在你的头脑中得到以下内容:
THERE ARE NO BLOCKS IN SCALA.
有函数,但没有块。
从技术上讲,这种说法是不正确的——Scala中有块,但它们不是你想象的那样,所以把它们从你的脑海中完全移除。稍后,您可以从头开始学习Scala中的块是什么。否则,你一定会试图让它们以它们不应该的方式工作,或者在它们以不同的方式工作时推断出它们以某种方式工作。
我非常喜欢第一个scala谜题中的解释。
也就是说,
的输出是什么?List(1, 2).map { i => println("Hi"); i + 1 }
List(1, 2).map { println("Hi"); _ + 1 }