使用Scala按名称参数

mel*_*ton 5 scala pass-by-name

我正在阅读"Scala中的函数编程"一书,并且遇到了一个我不完全理解的例子.

在关于严格/懒惰的章节中,作者描述了Streams的构造,并且代码如下:

sealed trait Stream[+A]
case object Empty extends Stream[Nothing]
case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]

object Stream {
    def cons[A](hd: => A, tl: => Stream[A]) : Stream[A] = {
        lazy val head = hd
        lazy val tail = tl
        Cons(() => head, () => tail)
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

我的问题是在smart constructor(cons)中,它调用Conscase类的构造函数.用于传递headtailval 的特定语法对我来说没有意义.为什么不像这样调用构造函数:

Cons(head, tail)
Run Code Online (Sandbox Code Playgroud)

据我所知,它使用的语法是强制创建两个只返回headtailval的Function0对象.这与仅仅传递headtail(没有() =>前缀)有什么不同,因为Cons案例类已经被定义为依次采用这些参数?这不是多余的吗?还是我错过了什么?

Nat*_*ate 9

差别在于=> A不等于() => A.

前者是按名称传递的,后者是不带参数(单位)并返回A的函数.

您可以在Scala REPL中测试它.

scala> def test(x: => Int): () => Int = x
<console>:9: error: type mismatch;
 found   : Int
 required: () => Int
       def test(x: => Int): () => Int = x
                                        ^
Run Code Online (Sandbox Code Playgroud)

只需x在我的示例中引用就会调用参数.在您的示例中,它正在构建一个推迟调用x的方法.


mis*_*ner 9

首先,你假设=> A并且() => A是相同的.但是,他们不是.例如,=> A只能在按名称传递参数的上下文中使用 - 不可能声明val类型=> A.由于case class参数总是vals(除非明确声明为vars),很明显为什么case class Cons[+A](h: => A, t: => Stream[A])不起作用.

其次,只是包装了一个用名字参数与空参数列表的功能是不一样的东西上面的代码实现:使用lazy val时,则确保两个hdtl评估最多一次.如果代码读了

Cons(() => hd, () => tl)
Run Code Online (Sandbox Code Playgroud)

每次调用对象的方法(字段)hd都会评估原始文件.使用,进行评价仅在第一次的这个方法被调用对象,相同的值在每个后续调用返回.hConslazy valhdhCons

在REPL中展示精简方式的差异:

> def foo = { println("evaluating foo"); "foo" }
> val direct : () => String = () => foo
> direct()
evaluating foo
res6: String = foo
> direct()
evaluating foo
res7: String = foo
> val lzy : () => String = { lazy val v = foo; () => v }
> lzy()
evaluating foo
res8: String = foo
> lzy()
res9: String = foo
Run Code Online (Sandbox Code Playgroud)

注意第二次调用中的"evaluate foo"输出lzy()是如何消失的,而不是第二次调用direct().