I understand the basic concept of call-by-name and call-by-value, and I have also looked into handful number of examples. However, I am not very clear about when to use call-by-name. What would be a real-world scenario where call-by-name would have a significant advantage or performance gain over the other call type? What should be the correct thinking approach to select a call type while designing a method?
There are plenty of places were call-by-name may gain performance or even correctness.
Simple performance example: logging. Imagine an interface like this:
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
And then used like this:
logger.info("Time spent on X: " + computeTimeSpent)
If the info
method doesn't do anything (because, say, the logging level was configured for higher than that), then computeTimeSpent
never gets called, saving time. This happens a lot with loggers, where one often sees string manipulation which can be expensive relative to the tasks being logged.
Correctness example: logic operators.
You have probably seen code like this:
if (ref != null && ref.isSomething)
Say you declared &&
method like this:
trait Boolean {
def &&(other: Boolean): Boolean
}
then, whenever ref
is null
, you'll get an error because isSomething
will be called on a null
reference before being passed to &&
. For this reason, the actual declaration is:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) other else this
}
So one may actually wonder is when to use call-by-value. In fact, in the Haskell programming language everything works similar to how call-by-name works (similar, but not the same).
There are good reasons not to use call-by-name: it is slower, it creates more classes (meaning the program takes longer to load), it consumes more memory, and it is different enough that many have difficult reasoning about it.
Call by name means the value is evaluated at the time it is accessed, while with call by value the value is evaluated first and then passed to the method.
To see the difference, consider this example (completely non-functional programming with only side effects ;) ). Say you want to create a function which measures how much time some operation takes. You can do it with call-by-name:
def measure(action: => Unit) = {
println("Starting to measure time")
val startTime = System.nanoTime
action
val endTime = System.nanoTime
println("Operation took "+(endTime-startTime)+" ns")
}
measure {
println("Will now sleep a little")
Thread.sleep(1000)
}
You will get the result (YMMV):
Starting to measure time
Will now sleep a little
Operation took 1000167919 ns
But if you change only the signature of measure
to measure(action: Unit)
so it uses pass-by-value, the result will be:
Will now sleep a little
Starting to measure time
Operation took 1760 ns
As you can see, the action
is evaluated before measure
even starts and also the elapsed time is close to 0 due to the action already having been run before the method was called.
Here, pass-by-name allows the expected behavior of the method to be achieved. In some cases it doesn't influence the correctness, but does influence the performance, for example in logging frameworks where a complex expression might not need to be evaluated at all if the result is not used.
The simple way it might be explained is
call-by-value functions compute the passed-in expression's value before calling the function, thus the same value is accessed every time. However, call-by-name functions recompute the passed-in expression's value every time it is accessed.
I've always thought this terminology is needlessly confusing. A function can have multiple parameters which vary in their call-by-name vs call-by-value status. So it's not that a function is call-by-name or call-by-value, it's that each of its parameters may be pass-by-name or pass-by-value. Furthermore, "call-by-name" has nothing to do with names. => Int is a different type from Int; it's "function of no arguments that will generate an Int" vs just Int. Once you've got first-class functions you don't need to invent call-by-name terminology to describe this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With