patternMinor
Why do programming languages use call-by-value over call-by-name?
Viewed 0 times
whylanguagesprogrammingvaluecallnameuseover
Problem
I am reading Software Engineering 1: Abstraction and Modelling by Dines Bjørner. One of the chapters talks a little bit about call-by-value vs call-by-name. In it, the author states that most programming languages use call-by-value, rather than call-by-name. But from what I have read in the chapter, it seems that call-by-name is a much safer method of calling functions.
For example, say we have a function like
It seems to me that call-by-name is both safer and more efficient (since we do not need to evaluate unused expressions). So for what reason do most (or any for that matter) programming languages use call-by-value instead of call-by-name?
For example, say we have a function like
f(x, y) = y * 3, and we call it somewhere like f(infinteLoop(), 6), where infinteLoop() is some function which never terminates. If we use call-by-name, we never have to call infiniteLoop() because x is never used in the body of f. But if we use call-by-value, we need to evaluate infinteLoop(), even though it will never be used in the body of f. This will cause the program not to terminate, of course.It seems to me that call-by-name is both safer and more efficient (since we do not need to evaluate unused expressions). So for what reason do most (or any for that matter) programming languages use call-by-value instead of call-by-name?
Solution
There's a couple of factors which come into place. There can be performance costs to call-by-name, as well as difficulty with understandability in the presence of effects.
First of all, call-by-name can use exponentially more time than call-by-value, since it may have to evaluate an expression multiple times. Imagine calling
So in practice, instead of call-by-name, we likely would prefer call-by-need. We can instead cache the result of evaluating
Even putting the theoretical aside, there are practical difficulties for implementing a call-by-name or call-by-need language on a traditional machine. Many programmers are used to using side effects in their program, such as input and output, and having their expressions run sequentially. In a call-by-name programming language, this is no longer the case, effects could be latent anywhere within an unevaluated expression, and only take effect when that data is examined. It took many years for the discovery that mathematical monads could be used as a sequencing interface. Prior methods for controlling input/output were difficult to use.
Call-by-value languages tend to also be able to be performant much more easily than call-by-name/call-by-need. Haskell has taken extensive optimization effort to run quickly, largely by being able to carefully introduce strictness to eliminate overhead of laziness. With laziness, a value of any type could be a closure: a complex allocated object and a pointer to remote code to run. This can require more allocations and more indirection to manipulate than machine integers and simple objects.
So, while we can't answer definitively why such languages aren't more popular, we can see that there has been plenty of complexity and research necessary to tackle, and so languages which used call-by-value have long had a head start.
First of all, call-by-name can use exponentially more time than call-by-value, since it may have to evaluate an expression multiple times. Imagine calling
f(x) = x + x when x takes a while to compute. In true call-by-name, the entire expression for x needs to be evaluated twice inside the body of f.So in practice, instead of call-by-name, we likely would prefer call-by-need. We can instead cache the result of evaluating
x, so that we don't have to compute it multiple times. This is what the programming language Haskell does (preceded by Miranda, Lazy ML). This solves the time complexity problem (as you may have intuited above, the time complexity is no worse than call-by-value), but it can also use much more space. This is because it may need large closures in order to delay evaluation. A machine integer uses a finite amount of space, but a Haskell Int could use arbitrarily large amounts, by accumulating a closure of computation that needs to be run. In the Glasgow Haskell Compiler, the garbage collector has special rules to specifically find and eliminate certain kinds of "space leaks" where we use excessive space via closures.Even putting the theoretical aside, there are practical difficulties for implementing a call-by-name or call-by-need language on a traditional machine. Many programmers are used to using side effects in their program, such as input and output, and having their expressions run sequentially. In a call-by-name programming language, this is no longer the case, effects could be latent anywhere within an unevaluated expression, and only take effect when that data is examined. It took many years for the discovery that mathematical monads could be used as a sequencing interface. Prior methods for controlling input/output were difficult to use.
Call-by-value languages tend to also be able to be performant much more easily than call-by-name/call-by-need. Haskell has taken extensive optimization effort to run quickly, largely by being able to carefully introduce strictness to eliminate overhead of laziness. With laziness, a value of any type could be a closure: a complex allocated object and a pointer to remote code to run. This can require more allocations and more indirection to manipulate than machine integers and simple objects.
So, while we can't answer definitively why such languages aren't more popular, we can see that there has been plenty of complexity and research necessary to tackle, and so languages which used call-by-value have long had a head start.
Context
StackExchange Computer Science Q#136647, answer score: 3
Revisions (0)
No revisions yet.