5

I am going through the book "Functional Programming in Scala" and have run across an example that I don't fully understand.

In the chapter on strictness/laziness the authors describe the construction of Streams and have code like this:

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)
    }
    ...
}

The question I have is in the smart constructor (cons) where it calls the constructor for the Cons case class. The specific syntax being used to pass the head and tail vals doesn't make sense to me. Why not just call the constructor like this:

Cons(head, tail)

As I understand the syntax used it is forcing the creation of two Function0 objects that simply return the head and tail vals. How is that different from just passing head and tail (without the () => prefix) since the Cons case class is already defined to take these parameters by-name anyway? Isn't this redundant? Or have I missed something?

4

4 に答える 4

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
                                        ^

サンプルで参照xするだけで、パラメーターが呼び出されます。あなたのサンプルでは、​​x の呼び出しを延期するメソッドを構築しています。

于 2014-10-30T21:19:27.883 に答える
9

=> Aまず、あなたはと() => Aが同じであると仮定しています。しかし、そうではありません。たとえば、 は=> Aパラメーターを名前で渡すコンテキストでのみ使用できます。val型のを宣言することはできません=> Acase classパラメータは常にs であるためval(明示的に s が宣言されていない限りvar)、機能しない理由は明らかcase class Cons[+A](h: => A, t: => Stream[A])です。

lazy val2 つ目は、名前によるパラメーターを空のパラメーター リストを持つ関数にラップするだけでは、上記のコードが達成することと同じではhdありtlませ。コードが読めれば

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

オリジナルは、オブジェクトのメソッド (フィールド)が呼び出されるたび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

. _ lzy()_direct()

于 2014-10-30T21:20:03.327 に答える
1

consメソッドのパラメーターは、名前によるパラメーター (hdおよび)であることに注意してくださいtl。つまり、 を呼び出すとcons、 を呼び出す前に引数が評価されないということですcons。それらは後で評価され、内部で使用した時点で評価されますcons

Consコンストラクターは type の 2 つの関数を受け取りますが、Unit => A名前によるパラメーターとしてではありません。したがって、これらはコンストラクターを呼び出す前に評価されます。

そうすればCons(head, tail)、評価さheadれます。tailつまりhdtl評価されます。

hdしかし、ここでの要点は、必要になるまでandを呼び出さないようにすることでした(誰かがorにtlアクセスしたとき)。したがって、2 つの無名関数をコンストラクターに渡します。これらの関数は、誰かがまたはにアクセスするまで呼び出されません。htConsConsht

于 2014-10-30T21:21:59.263 に答える