Let’s define a method that computes the area of a disc, given its radius:
Separate several parameters with commas:
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)
For instance, y
above refers to 4
, not square(2)
.
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 val
and def
becomes apparent when the right
hand side does not terminate. Given
def loop: Int = loop
A definition
def x = loop
is OK, but a definition
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.
For instance:
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 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 triangleArea
function,
which takes a triangle base and height as parameters and returns
its area:
Consider the following program that computes the area of a disc
whose radius is 10
:
Consider the following program that computes the area of a disc
whose radius is 10
:
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:
The parameterless execute method has been deprecated and will be removed in a future version of ScalaTest. Please invoke execute with empty parens instead: execute().
The trap method is no longer needed for demos in the REPL, which now abreviates stack traces, and will be removed in a future version of ScalaTest