views:

273

answers:

4

At just after 2:40 in ShadowofCatron's Scala Tutorial 3 video, it's pointed out that the parentheses following the name of a thunk are optional. "Buh?" said my functional programming brain, since the value of a function and the value it evaluates to when applied are completely different things.

So I wrote the following to try this out. My thought process is described in the comments.

object Main {

    var counter: Int = 10
    def f(): Int = { counter = counter + 1; counter }

    def runThunk(t: () => Int): Int = { t() }

    def main(args: Array[String]): Unit = {
        val a = f()     // I expect this to mean "apply f to no args"
        println(a)      // and apparently it does

        val b = f       // I expect this to mean "the value f", a function value
        println(b)      // but it's the value it evaluates to when applied to no args
        println(b)      // and the application happens immediately, not in the call

        runThunk(b)     // This is an error: it's not println doing something funny
        runThunk(f)     // Not an error: seems to be val doing something funny
    }

}

 

To be clear about the problem, this Scheme program (and the console dump which follows) shows what I expected the Scala program to do.

(define counter (list 10))
(define f (lambda ()
            (set-car! counter (+ (car counter) 1))
            (car counter)))

(define runThunk (lambda (t) (t)))

(define main (lambda args
               (let ((a (f))
                     (b f))
                 (display a) (newline)
                 (display b) (newline)
                 (display b) (newline)
                 (runThunk b)
                 (runThunk f))))

> (main)
11
#<procedure:f>
#<procedure:f>
13

 

After coming to this site to ask about this, I came across this answer which told me how to fix the above Scala program:

    val b = f _     // Hey Scala, I mean f, not f()

But the underscore 'hint' is only needed sometimes. When I call runThunk(f), no hint is required. But when I 'alias' f to b with a val then apply it, it doesn't work: the application happens in the val; and even lazy val works this way, so it's not the point of evaluation causing this behaviour.

 

That all leaves me with the question:

Why does Scala sometimes automatically apply thunks when evaluating them?

Is it, as I suspect, type inference? And if so, shouldn't a type system stay out of the language's semantics?

Is this a good idea? Do Scala programmers apply thunks rather than refer to their values so much more often that making the parens optional is better overall?


Examples written using Scala 2.8.0RC3, DrScheme 4.0.1 in R5RS.

+6  A: 

Your guess is correct - Scala has type-dependent semantics regarding the evalution of expressions.

Like Ruby, it always evaluates thunks even without parentheses. (This might have advantages for interaction purposes since you can switch pure and possibly impure operations without having to change the syntax.)

But since Scala has a powerful static type system, it can break the above rule and save the programmer from explicitly partially applying the functions in cases where the result of an evalution wouldn't make sense from a type perspective.

Note that type-dependent evalution can even simulate call-by-name


Now is type-dependent evalution behaviour is good or bad? ... Well, it can certainly lead to confusing cases like yours and doesn't feel that pure any more. But in most cases, it just works as intended by the programmer (making code more concise) - So let's say, it's okay.

Dario
+9  A: 

The default cause, when you write:

val b = f

is to evaluate the function and assign the result to b, as you have noticed. You can use the _, or you can explicitly specify the type of b:

// These all have the same effect
val b = f _
val b: () => Int = f
val b: Function0[Int] = f
Jesper
+8  A: 

In your example

def f(): Int = { counter = counter + 1; counter }

is defining a method, not a function. AFAIK methods are promoted to functions automatically in Scala depending on context. To define a function you can write

val f = () => { counter = counter + 1; counter }

and I think you will get what you want.

german1981
Aha, that explains it! So what you're saying is that creating an anonymous function value *actually creates a function value* (and then it's given a name with `val`) while what looks like a define shortcut (e.g. `(define (f args) body)` for Scheme) does something equivalent-ish in Scala, but something completely different at the JVM level. Thanks. This is exactly what I wanted to know.
Anonymouse
@Anonymouse That's not really correct. The value returned by the method `f` in your declaration is `Int`, just as you specified. If you declared `def f(): () => Int = () => { counter = counter + 1; counter }`, then you'd have a method returning a function. Still, `{ counter = counter + 1; counter }` is _not_ an anonymous function. It is a block which evaluates to whatever's the type of `counter`. So this is not a `val` vs `def` thing -- you just have to keep in mind methods are not functions.
Daniel
+1  A: 

The problem is here:

Buh?" said my functional programming brain, since the value of a function and the value it evaluates to when applied are completely different things.

Yes, but you did not declare any function.

def f(): Int = { counter = counter + 1; counter }

You declared a method called f which has one empty parameter list, and returns Int. A method is not a function -- it does not have a value. Never, ever. The best you can do is get a Method instance through reflection, which is not really the same thing at all.

val b = f _     // Hey Scala, I mean f, not f()

So, what does f _ means? If f was a function, it would mean the function itself, granted, but this is not the case here. What it really means is this:

val b = () => f()

In other words, f _ is a closure over a method call. And closures are implemented through functions.

Finally, why are empty parameter lists optional in Scala? Because while Scala allows declarations such as def f = 5, Java does not. All methods in Java require at least an empty parameter list. And there are many such methods which, in Scala style, would not have any parameters (for example, length and size). So, to make the code look more uniform with regards to empty parameter list, Scala makes them optional.

Daniel