f(a,b)
may first evaluate the arguments a
and b
, store the results in references or memory locations ref_a
and ref_b
, then evaluate the function's body with those references passed in. This gives the function the ability to look up the argument values, to modify them via assignment as if they were local variables, and to return values via the references. This is the call-by-reference evaluation strategy.
Evaluation strategy is specified by the programming language definition, and is not a function of any specific implementation. The calling convention defines implementation-specific parameter passing details.
Table
This is a table of evaluation strategies and representative languages by year introduced. The representative languages are listed in chronological order, starting with the language(s) that introduced the strategy and followed by prominent languages that use the strategy.Evaluation orders
While the order of operations defines the abstract syntax tree of the expression, the evaluation order defines the order in which expressions are evaluated. For example, the Python program1 2
due to Python's left-to-right evaluation order, but a similar program in OCaml:
2 1
due to OCaml's right-to-left evaluation order.
The evaluation order is mainly visible in code with side effects, but it also affects the performance of the code because a rigid order inhibits Strict evaluation
Applicative order is a family of evaluation orders in which a function's arguments are evaluated completely before the function is applied. This has the effect of making the functionNon-strict evaluation
A non-strict evaluation order is an evaluation order that is not strict, that is, a function may return a result before all of its arguments are fully evaluated. The prototypical example is normal order evaluation, which does not evaluate any of the arguments until they are needed in the body of the function. Normal order evaluation has the property that it terminates without error whenever any other evaluation order would have terminated without error. Note that lazy evaluation is classified in this article as a binding technique rather than an evaluation order. But this distinction is not always followed and some authors define lazy evaluation as normal order evaluation or vice-versa, or confuse non-strictness with lazy evaluation. Boolean expressions in many languages use a form of non-strict evaluation called short-circuit evaluation, where evaluation returns as soon as it can be determined that an unambiguous Boolean will result—for example, in a disjunctive expression (OR) wheretrue
is encountered, or in a conjunctive expression (AND) where false
is encountered, and so forth. Conditional expressions similarly use non-strict evaluation - only one of the branches is evaluated.
Comparison of applicative order and normal order evaluation
With normal order evaluation, expressions containing an expensive computation, an error, or an infinite loop will be ignored if not needed, allowing the specification of user-defined control flow constructs, a facility not available with applicative order evaluation. Normal order evaluation uses complex structures such as thunks for unevaluated expressions, compared to the call stack used in applicative order evaluation. Normal order evaluation has historically had a lack of usable debugging tools due to its complexity.Strict binding strategies
Call by value
In call by value, the evaluated value of the argument expression is bound to the corresponding variable in the function (frequently by copying the value into a new memory region). If the function or procedure is able to assign values to its parameters, only its local variable is assigned—that is, anything passed into a function call is unchanged in the caller'sImplicit limitations
In some cases, the term "call by value" is problematic, as the value which is passed is not the value of the variable as understood by the ordinary meaning of value, but an implementation-specific reference to the value. The effect is that what syntactically looks like call by value may end up rather behaving like call by reference or call by sharing, often depending on very subtle aspects of the language semantics. The reason for passing a reference is often that the language technically does not provide a value representation of complicated data, but instead represents them as a data structure while preserving some semblance of value appearance in the source code. Exactly where the boundary is drawn between proper values and data structures masquerading as such is often hard to predict. In C, an array (of which strings are special cases) is a data structure but the name of an array is treated as (has as value) the reference to the first element of the array, while a struct variable's name refers to a value even if it has fields that are vectors. In Maple, a vector is a special case of a table and therefore a data structure, but a list (which gets rendered and can be indexed in exactly the same way) is a value. In Tcl, values are "dual-ported" such that the value representation is used at the script level, and the language itself manages the corresponding data structure, if one is required. Modifications made via the data structure are reflected back to the value representation and vice versa. The description "call by value where the value is a reference" is common (but should not be understood as being call by reference); another term is call by sharing. Thus the behaviour of call by value Java or Visual Basic and call by value C or Pascal are significantly different: in C or Pascal, calling a function with a large structure as an argument will cause the entire structure to be copied (except if it's actually a reference to a structure), potentially causing serious performance degradation, and mutations to the structure are invisible to the caller. However, in Java or Visual Basic only the reference to the structure is copied, which is fast, and mutations to the structure are visible to the caller.Call by reference
Call by reference (or pass by reference) is an evaluation strategy where a parameter is bound to an implicit reference to the variable used as argument, rather than a copy of its value. This typically means that the function can modify (i.e., assign to) the variable used as argument—something that will be seen by its caller. Call by reference can therefore be used to provide an additional channel of communication between the called function and the calling function. A call-by-reference language makes it more difficult for a programmer to track the effects of a function call, and may introduce subtle bugs. A simple litmus test for whether a language supports call-by-reference semantics is if it's possible to write a traditionalswap(a, b)
function in the language.
Call by reference can be simulated in languages that use call by value and don't exactly support call by reference, by making use of references (objects that refer to other objects), such as pointers (objects representing the memory addresses of other objects). Languages such as C, ML and Rust use this technique. It is not a separate evaluation strategy—the language calls by value—but sometimes it is referred to as "call by address" or "pass by address". In ML, references are type- and memory-safe, similar to Rust.
In purely functional languages there is typically no semantic difference between the two strategies (since their data structures are immutable, so there is no possibility for a function to modify any of its arguments), so they are typically described as call by value even though implementations frequently use call by reference internally for the efficiency benefits.
Following is an example that demonstrates call by reference in the E programming language:
def modify(var p, &q) ? var a := 1 # value: 1 ? var b := 2 # value: 2 ? modify(a, &b) ? a # value: 1 ? b # value: 27Following is an example of call by address that simulates call by reference in C:
Call by sharing
Call by sharing (also known as "call by object" or "call by object-sharing") is an evaluation strategy first noted by Barbara Liskov in 1974 for the[1]
because the append
method modifies the object on which it is called.
Assignments within a function are not noticeable to the caller, because, in these languages, passing the variable only means passing (access to) the actual object referred to by the variable, not access to the original (caller's) variable. Since the rebound variable only exists within the scope of the function, the counterpart in the caller retains its original binding.
Compare the Python mutation above with the code below, which binds the formal argument to a new object:
[]
, because the statement a_list + [1]
reassigns a new list to the variable rather than to the location it references.
For immutable objects, there is no real difference between call by sharing and call by value, except if object identity is visible in the language. The use of call by sharing with mutable objects is an alternative to output parameter, input/output parameters: the parameter is not assigned to (the argument is not overwritten and object identity is not changed), but the object (argument) is mutated.
Call by copy-restore
Call by copy-restore—also known as "copy-in copy-out", "call by value result", "call by value return" (as termed in the Fortran community)—is a special case of call by reference where the provided reference is unique to the caller. This variant has gained attention in multiprocessing contexts and Remote procedure call: if a parameter to a function call is a reference that might be accessible by another thread of execution, its contents may be copied to a new reference that is not; when the function call returns, the updated contents of this new reference are copied back to the original reference ("restored"). The semantics of call by copy-restore also differ from those of call by reference, where two or more function arguments alias one another (i.e., point to the same variable in the caller's environment). Under call by reference, writing to one will affect the other; call by copy-restore avoids this by giving the function distinct copies, but leaves the result in the caller's environment undefined depending on which of the aliased arguments is copied back first—will the copies be made in left-to-right order both on entry and on return? When the reference is passed to the caller uninitialized, this evaluation strategy may be called "call by result".Non-strict binding strategies
Call by name
Call by name is an evaluation strategy where the arguments to a function are not evaluated before the function is called—rather, they are substituted directly into the function body (usingExpression
parameters. The latter results in an abstract syntax tree being given to the function. Eiffel provides agents, which represent an operation to be evaluated when needed. Seed7 provides call by name with function parameters. java.util.function.Supplier
interface.
Call by need
Call by need is a memoized variant of call by name, where, if the function argument is evaluated, that value is stored for subsequent use. If the argument is pure (i.e., free of side effects), this produces the same results as call by name, saving the cost of recomputing the argument. Haskell is a well-known language that uses call-by-need evaluation. Because evaluation of expressions may happen arbitrarily far into a computation, Haskell only supports side effects (such asLazy
.
Graph reduction is an efficient implementation of lazy evaluation.
Call by macro expansion
Call by macro expansion is similar to call by name, but uses textual substitution rather than capture, thereby avoiding substitution. But macro substitution may cause mistakes, resulting inCall by future
"Call by future", also known as "parallel call by name" or "lenient evaluation", is a concurrent evaluation strategy combining non-strict semantics with eager evaluation. The method requires fine-grained dynamic scheduling and synchronization but is suitable for massively parallel machines. The strategy creates a future (promise) for the function's body and each of its arguments. These futures are computed concurrently with the flow of the rest of the program. When a future A requires the value of another future B that has not yet been computed, future A blocks until future B finishes computing and has a value. If future B has already finished computing the value is returned immediately. Conditionals block until their condition is evaluated, and lambdas do not create futures until they are fully applied. If implemented with processes or threads, creating a future will spawn one or more new processes or threads (for the promises), accessing the value will synchronize these with the main thread, and terminating the computation of the future corresponds to killing the promises computing its value. If implemented with a coroutine, as in .NET async/await, creating a future calls a coroutine (an async function), which may yield to the caller, and in turn be yielded back to when the value is used, cooperatively multitasking. The strategy is non-deterministic, as the evaluation can occur at any time between creation of the future (i.e., when the expression is given) and use of the future's value. The strategy is non-strict because the function body may return a value before the arguments are evaluated. However, in most implementations, execution may still get stuck evaluating an unneeded argument. For example, the programOptimistic evaluation
Optimistic evaluation is a call-by-need variant where the function's argument is partially evaluated in a call-by-value style for some amount of time (which may be adjusted at runtime). After that time has passed, evaluation is aborted and the function is applied using call by need. This approach avoids some of call-by-need's runtime expenses while retaining desired termination characteristics.See also
* Beta normal form * Comparison of programming languages * eval * Lambda calculus * Call-by-push-value * Partial evaluationReferences
Further reading
* * * * * * *External links
* The interactive on-line Geometry of Interactionbr>visualiser