使用Scala按名称参数

时间:2014-10-30 21:01:45

标签: scala pass-by-name

我正在浏览这本书"Functional Programming in 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)
    }
    ...
}

我遇到的问题是智能构造函数(cons),它调用Cons案例类的构造函数。用于传递headtail val的特定语法对我来说没有意义。为什么不像这样调用构造函数:

Cons(head, tail)

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

4 个答案:

答案 0 :(得分:9)

首先,您假设=> A() => A是相同的。但是,他们不是。例如,=> A只能在按名称传递参数的上下文中使用 - 不可能声明类型为val的{​​{1}}。由于=> A参数始终为case class s(除非明确声明为val s),否则很清楚为什么var无效。

其次,只是将带有参数的参数包装到带有空参数列表的函数中与上面的代码完成的不同:使用case class Cons[+A](h: => A, t: => Stream[A]),确保lazy valhd最多只评估一次。如果代码读了

tl

每次调用Cons(() => hd, () => tl) 对象的hd方法(字段)时,将评估原始h。使用Cons,仅在第一次调用此lazy val对象的hd方法时评估h,并且在每次后续调用中都返回相同的值。

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

Cons

注意第二次调用> 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 中的“evaluate foo”输出如何消失,而不是第二次调用lzy()

答案 1 :(得分:8)

区别在于=> 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
                                        ^

在我的示例中简单引用x会导致调用参数。在您的示例中,它正在构建一个延迟调用x的方法。

答案 2 :(得分:1)

请注意,方法cons的参数是按名称参数(hdtl)。这意味着如果您调用cons,则在致电cons之前不会评估参数;在cons内使用它们时,它们将在稍后进行评估。

请注意,Cons构造函数采用类型为Unit => A的两个函数,但不是作为按名称参数。所以在调用构造函数之前会对它们进行评估。

如果您执行Cons(head, tail),则会评估headtail,这意味着将评估hdtl

但这里的重点是避免在必要时(当有人访问hd对象中的tlh时)调用tCons。因此,您将两个匿名函数传递给Cons构造函数;在有人访问ht之前,不会调用这些函数。

答案 3 :(得分:0)

def cons[A](hd: => A, tl: => Stream[A]) : Stream[A]

hd的类型是AtlStream[A]

而在case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]

h的类型为Function0[A],t的类型为Function0[Stream[A]]

鉴于hd的类型为A,智能构造函数将案例类调用为

 lazy val head = hd
 lazy val tail = tl
 Cons(() => head, () => tail) //it creates a function closure so that head is accessible within Cons for lazy evaluation