为什么 Scala 引入惰性参数。不应该由JVM管理(对用户不可见)如何初始化值?将控制权交给开发人员并将值定义为惰性值的实际用例是什么?
4 回答
别名参数:主要动机之一是支持 dsls。它们允许您在 API 中拥有非常好的语法,几乎感觉就像它们内置在语言中一样。例如,您可以非常轻松地定义自己的自定义repeat
循环:
def repeat(body: =>Unit)(until: =>Boolean): Unit = {
body
if (until) {} else repeat(body)(until)
}
然后把它当作语言的一部分来使用。
var i = 0
repeat {
println(i)
i += 1
} (i < 3)
或者你可以类似地产生一个像这样的新线程:spawn { println("on the new thread!") }
,或者你可以像这样对你FileInputStream
的 s 进行自动资源管理:withFile("/home/john/.bashrc") { println(_.contents) }
。
lazy
价值观——这里的动机是:
Stream
像s这样的惰性数据结构在函数式语言中很流行,您可以使用它们来实现高效的数据结构 a-la Okasaki 的函数式队列。- 避免分配或初始化一些昂贵的资源,如果它们从未在某些对象中使用过,例如文件句柄或数据库连接。
- 对于由许多 mixin 组成的对象,以正确的顺序初始化对象字段。
- 当有许多线程共享一个值时,实现正确的“仅初始化一次”语义(请参阅此处的介绍)。
- 为嵌套的单例对象提供翻译方案:
class A { object B }
变成这样:
class A {
class A$B$
lazy val B = new A$B$
}
一种常见的情况是当类的编写者不知道是否将使用昂贵的初始化val
。在这种情况下,将val
按需初始化。
另一种情况是有机地控制初始化的顺序。通常在初始化某个特定对象之前很久就创建了一个对象val
,因为其他类还没有被初始化。在这种情况下,惰性为这种排序自然发生提供了一种方便的方式,而作者无需提出对复杂的多阶段初始化进行排序的总体规划。
TLDR:因为它吓坏了用户并且由于性能原因
今天的大多数语言都是渴望的。其中一些不是,他们称之为懒惰. 虽然许多编程问题可以通过惰性求值以优美简洁的方式表达,但我认为绝对惰性并不是一个好主意。从主观的角度来看,程序员习惯于以急切的方式思考(尤其是那些来自命令式领域的人),所以用 Haskell 等天真地编写程序可能会让你很困惑。每道菜都只有叉子,不如在叉子和勺子之间选择好,虽然 scala 支持语言级别的惰性评估,但它默认为渴望模型。原因(除了 Martin 和其他语言设计师的个人选择)是 Java 和 Scala 之间的互操作——用一种语言组合这两个世界将是一场噩梦。此外,在 Scala 设计时,JVM 还没有支持这些功能,或多或少只有在 Java 7 中引入了方法句柄,才使高性能惰性 val成为可能(仅在两年前,而 scala 已经存在了十年)。
我会回答我自己的问题。因此,惰性值非常有用的一个用例是,如果您想创建一个带有循环的不可变数据结构。没有懒惰是不容易的,因为否则你将不得不修改一个已经创建的对象。如果您希望您的对象是不可变的,这是不可能的。让我以简单的循环实现为例。
因此,在 Scala 中,您可以通过以下方式实现这一点
class Node(inNode: => Node) { lazy val in = inNode }
lazy val node :Node = new Node(new Node(node))
这样你就创建了一个不可变的循环。您可以通过比较参考来验证结果。
scala> node.in
res3: Node = Node@2d928643
scala> node.in.in
res4: Node = Node@3a5ed7a6
scala> node
res5: Node = Node@3a5ed7a6