Use of Scala by-name parameters
Asked Answered
F

4

6

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?

Faggoting answered 30/10, 2014 at 21:1 Comment(0)
B
9

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.

Banneret answered 30/10, 2014 at 21:19 Comment(6)
Yep. Sorry about that. As I mentioned to @Jesper I should have noticed the difference in the two constructors and didn't. So Cons requires an explicit Function0 and cons constructs one for you (behind the scenes). What gets passed to Cons is evaluated right away and what gets passed to cons is evaluated later. Is that right?Faggoting
Yes that's right. When you pass by value it will be re-evaluated every time it is read as well.Banneret
Minor nitpick: () => A is the type of a function with a single, empty argument list. (Unit) => A would be the type of a function taking unit as an argument. () is not a valid type.Barricade
@Barricade I've modified; you're right the wording was incorrect. I was trying to highlight that () is the Unit value. So () => Int is an identical type to Unit => Int but is slightly shorter.Banneret
@Banneret () => Int and Unit => Int are not the same types: scalafiddle.io/sf/7V2DdXv/0. But anyways, thanks for fixing the answer! :)Barricade
@Barricade Again, you are right! Thanks for teaching me something!Banneret
G
10

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

Grunberg answered 30/10, 2014 at 21:20 Comment(4)
I'm not sure why a val cannot be of type => A. I got too lost in the weeds and didn't realize the difference between the definition of Cons and cons. I did understand the use of the lazy val objects, though. Thanks.Faggoting
Along the same lines, aren't hd and tl val parameters? And they are by-name.Faggoting
I don't know what you mean by val parameter. For me, the term val parameter only makes sense in the context of classes (class A(x: String) vs. class B(val x: String)). cons, as you pointed out, is a normal method. So why would hd and tl be val parameters?Grunberg
Sorry about that. I wasn't aware that parameters to functions could not be var. I found some discussion about this after reading your comment and now realize that var parameters to functions are not allowed. I was extending the class B(var x: String) construct to a normal function (incorrectly).Faggoting
B
9

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.

Banneret answered 30/10, 2014 at 21:19 Comment(6)
Yep. Sorry about that. As I mentioned to @Jesper I should have noticed the difference in the two constructors and didn't. So Cons requires an explicit Function0 and cons constructs one for you (behind the scenes). What gets passed to Cons is evaluated right away and what gets passed to cons is evaluated later. Is that right?Faggoting
Yes that's right. When you pass by value it will be re-evaluated every time it is read as well.Banneret
Minor nitpick: () => A is the type of a function with a single, empty argument list. (Unit) => A would be the type of a function taking unit as an argument. () is not a valid type.Barricade
@Barricade I've modified; you're right the wording was incorrect. I was trying to highlight that () is the Unit value. So () => Int is an identical type to Unit => Int but is slightly shorter.Banneret
@Banneret () => Int and Unit => Int are not the same types: scalafiddle.io/sf/7V2DdXv/0. But anyways, thanks for fixing the answer! :)Barricade
@Barricade Again, you are right! Thanks for teaching me something!Banneret
F
1

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.

Flop answered 30/10, 2014 at 21:21 Comment(1)
I got lost in the weeds and should have noticed the difference between the case class constructor and the smart constructor declarations. Thanks.Faggoting
A
0

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
Allin answered 18/3, 2020 at 13:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.