Use of Scala by-name parameters

后端 未结 4 1876
臣服心动
臣服心动 2020-12-20 22:43

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

相关标签:
4条回答
  • 2020-12-20 23:00

    Note that the parameters of the method cons are by-name parameters (hd and tl). That means that if you call cons, the arguments will not be evaluated before you call cons; they will be evaluated later, at the moment you use them inside cons.

    Note that the Cons constructor takes two functions of type Unit => A, but not as by-name parameters. So these will be evaluated before you call the constructor.

    If you do Cons(head, tail) then head and tail will be evaluated, which means hd and tl will be evaluated.

    But the whole point here was to avoid calling hd and tl until necessary (when someone accesses h or t in the Cons object). So, you pass two anonymous functions to the Cons constructor; these functions will not be called until someone accesses h or t.

    0 讨论(0)
  • 2020-12-20 23:11

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

    the type of hd is A, tl is Stream[A]

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

    h is of type Function0[A] and t of type Function0[Stream[A]]

    given the type of hd is A, the smart constructor invokes the case class as

     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
    
    0 讨论(0)
  • 2020-12-20 23:13

    First, you are assuming that => A and () => A are the same. However, they are not. For example, the => A can only be used in the context of passing parameters by-name - it is impossible to declare a val of type => A. As case class parameters are always vals (unless explicitly declared vars), it is clear why case class Cons[+A](h: => A, t: => Stream[A]) would not work.

    Second, just wrapping a by-name parameter into a function with an empty parameter list is not the same as what the code above accomplishes: using lazy vals, it is ensured that both hd and tl are evaluated at most once. If the code read

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

    the original hd would be evaluated every time the h method (field) of a Cons object is invoked. Using a lazy val, hd is evaluated only the first time the h method of this Cons object is invoked, and the same value is returned in every subsequent invocation.

    Demonstrating the difference in a stripped-down fashion in the 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
    

    Note how the "evaluating foo" output in the second invocation of lzy() is gone, as opposed to the second invocation of direct().

    0 讨论(0)
  • 2020-12-20 23:19

    The difference is in => A not being equal to () => A.

    The former is pass by name, and the latter is a function that takes no parameters and returns an A.

    You can test this out in the Scala REPL.

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

    Simply referencing x in my sample causes the parameter to be invoked. In your sample, it's constructing a method which defers invocation of x.

    0 讨论(0)
提交回复
热议问题