Consider the following program that computes the area of a disc
whose radius is
3.14159 * 10 * 10
To make complex expressions more readable we can give meaningful names to intermediate expressions:
val radius = 10 val pi = 3.14159 pi * radius * radius
Besides making the last expression more readable it also allows us to not repeat the actual value of the radius.
A name is evaluated by replacing it with the right hand side of its definition
Here are the evaluation steps of the above expression:
pi * radius * radius 3.14159 * radius * radius 3.14159 * 10 * radius 31.4159 * radius 31.4159 * 10 314.159
Definitions can have parameters. For instance:
def square(x: Double) = x * x square(3.0) shouldBe res0
Let’s define a method that computes the area of a disc, given its radius:
def square(x: Double) = x * x def area(radius: Double): Double = 3.14159 * square(radius) area(10) shouldBe res0
Separate several parameters with commas:
def sumOfSquares(x: Double, y: Double) = square(x) + square(y)
Function parameters come with their type, which is given after a colon
def power(x: Double, y: Int): Double = ...
If a return type is given, it follows the parameter list.
The right hand side of a
def definition is evaluated on each use.
The right hand side of a
val definition is evaluated at the point of the definition
itself. Afterwards, the name refers to the value.
val x = 2 val y = square(x)
y above refers to
Applications of parametrized functions are evaluated in a similar way as operators:
sumOfSquares(3, 2 + 2) sumOfSquares(3, 4) square(3) + square(4) 3 * 3 + square(4) 9 + square(4) 9 + 4 * 4 9 + 16 25
This scheme of expression evaluation is called the substitution model.
The idea underlying this model is that all evaluation does is reduce an expression to a value.
It can be applied to all expressions, as long as they have no side effects.
The substitution model is formalized in the λ-calculus, which gives a foundation for functional programming.
Does every expression reduce to a value (in a finite number of steps)?
No. Here is a counter-example:
def loop: Int = loop loop
The difference between
def becomes apparent when the right
hand side does not terminate. Given
def loop: Int = loop
def x = loop
is OK, but a value
val x = loop
will lead to an infinite loop.
The interpreter reduces function arguments to values before rewriting the function application.
One could alternatively apply the function to unreduced arguments.
sumOfSquares(3, 2 + 2) square(3) + square(2 + 2) 3 * 3 + square(2 + 2) 9 + square(2 + 2) 9 + (2 + 2) * (2 + 2) 9 + 4 * (2 + 2) 9 + 4 * 4 25
The first evaluation strategy is known as call-by-value, the second is known as call-by-name.
Both strategies reduce to the same final values as long as
Call-by-value has the advantage that it evaluates every function argument only once.
Call-by-name has the advantage that a function argument is not evaluated if the corresponding parameter is unused in the evaluation of the function body.
Scala normally uses call-by-value.
Complete the following definition of the
which takes a triangle base and height as parameters and returns
def triangleArea(base: Double, height: Double): Double = base * height / res0 triangleArea(3, 4) shouldBe 6.0 triangleArea(5, 6) shouldBe res1