Scala
model of expression evaluation is based on the principle of substitution
model, in which variable names are replaced by the values they are bound to.The underlying
idea is that all evaluation does is reduce an expression to a value, which is a term that
does not need further evaluation. Values includes not only constants, but tuple, constructors, and
functions. The substitution model is formalized in the $\lambda-calculus$, which gives foundation for functional programming.
In Scala
program expressions are evaluated in the same way we would evaluate a mathematical expression. For example we know the expression $(2 * 2) + (3 * 4)$ will be evaluated by first evaluating $2 * 2$ and $3 * 4$ and finally $4 + 12$. Scala
evaluation works in same way. Similarly in Scala
non-primitive expression is evaluated as follows.
During the process, Scala
evaluator rewrites the program expression to another expression. The evaluation process finally converts the expression into value and the evaluation stops: assuming evaluation terminates else evaluation fails to reduce to final value and we have infinite loop.
Expression evaluation works by rewriting the original expression. Rewriting works by performing simple steps called reductions. For example, the evaluation of arithmetic expression is:
$\to (2 * 2) + (4 * y)$
$\to 4 + (4 * 5)$
$\to 4 + 20$
$\to 24$
Function with parameters are evaluated similar to the operators in expressions. Following are the rules for function parameters evaluation:
Below is the evaluation of the function parameters:
$\to square(6)$
$\to 6 * 6$
$\to 36$
There are two evaluation strategy for function with parameters namely call-by-value
and call-by-name
. For expressions that use only pure function and can be reduced with substitution model, both yields the same final result. Let’s define the function sumofSqaures
and evaluate it with both call-by-value
and call-by-name
.
call-by-value: Call-by-value evaluates every function argument only once thus it avoids the repeated evaluation of arguments. Example: $\to sumofSqaures(2, 5)$
$\to square(2) + square(5)$
$\to 2*2 + 5 * 5$
$\to 4 + 25$
$\to 29$
call-by-name: Call-by-name avoids evaluation of parameters if it is not used in the function body. Example:
$\to square(2) + sqaure(2 + 3)$
$\to square(2) + square( 2 + 3)$
$\to 2 * 2 + square(2 + 3)$
$\to 4 + (2 + 3) * (2 + 3)$
$\to 4 + 5 * (2 * 3)$
$\to 4 + 5 * 5$
$\to 4 + 25$
$\to 29$
Call-by-value is more efficient then call-by-name . If call-by-value evaluation of expression terminates then call-by-name evaluation also terminates, too but the other direction is not true since call-by-value might loop infinitely but call-by-name would terminate. Example:
Then the evaluation of function test(1, loop)
is:
Call-by-name evaluation reduces to $1$.
$\to 1$
Call-by-value evaluation leads to infinite loop.
$\to test(1, loop)$
$\to test(1, loop)$
$\to …$
Scala
uses call-by-value as a default since call-by-value is often exponentially efficient then call-by-name. We can force it to use call-by-name by preceding the parameter types by $\Rightarrow$. Example:
Following function call reduces to 1.
And the below function call leads to infinite loop.
The actual operation of Scala
interpretor and compiler is more complex under the hood. Nevertheless, understanding the evaluation model helps us to know what the program is doing and reason its correctness. The evaluation model is abstraction that hides the compiler details and help us understands how expressions and function with parameters are evaluated by Scala
compiler.