Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this function called multiple times?

Tags:

scala

In this function "f" :

def f(x: => Int) : Int = x * x * x  //> f: (x: => Int)Int

var y = 0                           //> y  : Int = 0

f {
    y += 1
    println("invoked")
    y
}                                   //> invoked
                                    //| invoked
                                    //| invoked
                                    //| res0: Int = 6

"f" is invoked same amount of times as "x" parameter is multiplied.

But why is function invoked multiple times ?

Should "f" not expand to 1 * 1 * 1 not 1 * 2 * 3 ?

like image 795
blue-sky Avatar asked Sep 14 '25 09:09

blue-sky


1 Answers

Your x is not a function, it is a by-name parameter, and its type is a parameterless method type.

Parameterless method type means the same as def x, something that is evaluated every time you reference it. By reference, we mean x and not x.apply() or x().

The expression you're passing to your function f is evaluated every time x is referenced in f. That expression is the whole thing in braces, a block expression. A block is a sequence of statements followed by the result expression at the end.

Here's another explanation: https://stackoverflow.com/a/13337382/1296806

But let's not call it a function, even if it behaves like one under the covers.

Here is the language used in the spec:

http://www.scala-lang.org/files/archive/spec/2.11/04-basic-declarations-and-definitions.html#by-name-parameters

It's not a value type because you can't write val i: => Int.

It was a big deal when they changed the implementation so you could pass a by-name arg to another method without evaluating it first. There was never a question that you can pass function values around like that. For example:

scala> def k(y: => Int) = 8
k: (y: => Int)Int

scala> def f(x: => Int) = k(x)   // this used to evaluate x
f: (x: => Int)Int

scala> f { println("hi") ; 42 }
res8: Int = 8

An exception was made to "preserve the by-name behavior" of the incoming x.

This mattered to people because of eta expansion:

scala> def k(y: => Int)(z: Int) = y + y + z
k: (y: => Int)(z: Int)Int

scala> def f(x: => Int) = k(x)(_)  // normally, evaluate what you can now
f: (x: => Int)Int => Int

scala> val g = f { println("hi") ; 42 }
g: Int => Int = <function1>

scala> g(6)
hi
hi
res11: Int = 90

The question is how many greetings do you expect?

More quirks:

scala> def f(x: => Int) = (1 to 5) foreach (_ => x)
f: (x: => Int)Unit

scala> def g(x: () => Int) = (1 to 5) foreach (_ => x())
g: (x: () => Int)Unit

scala> var y = 0
y: Int = 0

scala> y = 0 ; f { y += 1 ; println("hi") ; y }
hi
hi
hi
hi
hi
y: Int = 5

scala> y = 0 ; g { y += 1 ; println("hi") ; () => y }
hi
y: Int = 1

scala> y = 0 ; g { () => y += 1 ; println("hi") ; y }
hi
hi
hi
hi
hi
y: Int = 5

Functions don't cause this problem:

scala> object X { def f(i: Int) = i ; def f(i: => Int) = i+1 }
defined object X

scala> X.f(0)
res12: Int = 0

scala> trait Y { def f(i: Int) = i }
defined trait Y

scala> object X extends Y { def f(i: => Int) = i+1 }
defined object X

scala> X.f(0)
<console>:11: error: ambiguous reference to overloaded definition,
both method f in object X of type (i: => Int)Int
and  method f in trait Y of type (i: Int)Int
match argument types (Int)
              X.f(0)
                ^

Compare method types:

http://www.scala-lang.org/files/archive/spec/2.11/03-types.html#method-types

This is not a pedantic distinction; irrespective of the current implementation, it can be confusing to think of a by-name parameter as "really" a function.

like image 180
som-snytt Avatar answered Sep 16 '25 04:09

som-snytt