Using Scala by-name parameters

I am studying the book "Functional Programming in Scala" and came across an example that I do not 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 am having is the smart constructor ( cons

) where it calls the constructor for the case class Cons

. The specific syntax used to pass values head

and tail

does not make sense to me. Why not just call the constructor like so:

Cons(head, tail)

      

As I understand it, the syntax used causes two Function0 objects to be created that simply return values head

and tail

. How is this different from a simple pass head

and tail

(no prefix () =>

) since the case class is Cons

already defined to accept these parameters by name anyway? Isn't that redundant? Or am I missing something?

+5


source to share


3 answers


The difference is => A

not equal() => A

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

You can check this 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
                                        ^

      

A simple reference to x

in my example results in a parameter call. In your example, this is creating a method that defers calling x.

+8


source


First, you assume that => A

both are the () => A

same. However, it is not. For example, it => A

can be used only in the context of passing parameters by name - it is impossible to declare a val

type => A

. Since parameters are case class

always val

(unless s is explicitly declared var

), it's understandable why it case class Cons[+A](h: => A, t: => Stream[A])

doesn't work.

Secondly, simply transferring the by-name parameter to a function with an empty parameter list is not the same as what the above code does: using lazy val

s, it is ensured that hd

, as well as being tl

evaluated, no more than once . If the code is readable

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

      

the original one hd

will be evaluated every time the method (t214) of the h

object's method is Cons

called. Usage lazy val

, hd

is evaluated only the first time a method of h

this object is called Cons

, and the same value is returned in each subsequent call.



Demonstrating the stripped-down style difference 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 that there is no result of "evaluating foo" in the second call lzy()

, unlike in the second call direct()

.

+9


source


Note that the method parameters cons

are named parameters ( hd

and tl

). This means that if you call cons

, no arguments will be evaluated until called cons

; they will be evaluated later, the moment you use them internally cons

.

Note that the constructor cons

serves two functions of the type Unit => A

, but not as name parameters. Therefore, they will be evaluated before calling the constructor.

If you do Cons(head, tail)

, then head

u will be evaluated tail

, which means hd

u will be evaluated tl

.

But the whole point is not to name hd

and tl

until needed (when someone turns to h

or t

to an object cons

). So, you are passing two anonymous functions to the constructor cons

; these functions will not be called until someone calls h

or t

.

+1


source







All Articles