2014-10-30 53 views
3

我正在阅读"Functional Programming in Scala"这本书,并且遇到了一个我不完全了解的例子。Scala名称参数的使用

在上严格的章节/懒惰作者描述流的结构,并有这样的代码:

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 vals的特定语法对我来说没有意义。为什么不这样调用构造函数:

Cons(head, tail) 

据我了解语法使用的是强要2名Function0对象只返回headtail丘壑的创建。这与刚刚通过headtail(没有() =>前缀)有什么不同,因为Cons案例类已被定义为无论如何都采用这些参数?这不是多余的吗?或者我错过了什么?

回答

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调用的方法。

+0

是的。对于那个很抱歉。正如我提到的@Jesper,我应该注意到两个构造函数的区别,并没有。所以'Cons'需要一个明确的Function0和'cons'为你构建一个(在幕后)。什么传递给'Cons'会立即被评估,传递给'cons'的是稍后评估的。是对的吗? – melston 2014-10-30 21:33:54

+0

是的。当你按照价值传递时,它每次读取时都会被重新评估。 – Nate 2015-10-05 22:01:35

8

首先,您假设=> A() => A是相同的。但是,他们不是。例如,=> A只能在按名称传递参数的情况下使用 - 不可能声明=> A类型的val。由于case class参数始终为val s(除非明确声明为var s),因此很明显为什么case class Cons[+A](h: => A, t: => Stream[A])不起作用。

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

Cons(() => hd,() => tl) 

原始hd一个Cons对象的每个的h方法(场)时间将被评估被调用。使用lazy valhd仅在第一次调用此Cons对象的h方法时进行评估,并且在随后的每次调用中都会返回相同的值。

演示在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 

注意如何在lzy()第二次调用的“评估foo”的输出走了,而不是的direct()第二次调用。

+0

我不知道为什么'val'不能是'=> A'类型。我迷失在杂草中,没有意识到“缺点”和“缺点”的定义之间的差异。不过,我*了解了'lazy val'对象的使用。谢谢。 – melston 2014-10-30 21:37:15

+0

沿着同样的路线,不是'hd'和'tl' val参数吗?他们*是*名称。 – melston 2014-10-30 21:44:06

+0

我不知道'val'参数是什么意思。对于我来说,术语'val'参数在类('类A(x:String)'与'类B(val x:String)'')的上下文中唯一有意义。正如你所指出的那样,“缺点”是一种正常的方法。那么为什么'hd'和'tl'是'val'参数? – misberner 2014-10-30 21:55:43

1

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

请注意,Cons构造函数使用Unit => A类型的两个函数,但而不是作为名称参数。所以这些将在您调用构造函数之前进行评估。

如果你这样做Cons(head, tail)然后headtail将被评估,这意味着hdtl进行评估。

但这里的整点是为了避免调用hdtl直到必要(当有人在Cons对象访问ht)。因此,您将两个匿名函数传递给构造函数Cons;这些功能将不会被调用,直到有人访问ht

+0

我迷失在杂草中,应该注意到case类构造函数和智能构造函数声明之间的区别。谢谢。 – melston 2014-10-30 21:30:25