0:00

So in this week, we are going to do some Scala programming.

We're going to take the first steps into this new programming language and into

functional programming in general. A lot of the material in this session will

look very easy to you because it's familiar.

But there are also some things that are fundamental, in particular, the former

model of evaluation that we call the substitution model, which will be very

important for the sessions later on. So, it's good to pay a little attention.

Okay, let's get into it. Every non-trivial language provides

primitive expressions that represents the simplest elements of the language.

And then some ways to combine expressions and ways to abstract expressions.

Abstraction means that we introduce a name for an expression and then further on we

can reference the expression by its name. One way to approach functional programming

is to see it a little bit like a calculator.

In fact most functional languages have an interactive shell.

That's also sometimes called a REPL, which stands for read, eval, print, loop, and in

that shell you can type expressions, and it will respond with the results of

evaluating these expressions. In Scala you can start the REPL simply by

typing Scala. So if you write Scala here, Then you get

the ripple, that assumes that you have a standard Scalar distribution.

That's actually not a requirement for this course.

For the course, we ask you to install SVT, the Scala build tool instead.

From the SVT, you can also get the ripple simply by typing SVT consol.

It's the same REPL, so it doesn't matter whether you start that with Scala and SVT

consol. Once you're on the REPL, you can type an

expression like 42. You can also define values.

1:58

Such as radius = ten. Enter REPL, in each case, respond.

If you have an expression, with the expression's type, in this case that is

INT, and the expression's value. If you do have a definition like radius,

it would just give you the type of the definition.

You can also define floating point numbers.

And afterwards you can refer To what you have defined before.

Now, let's have a look at evaluation. You have seen how that these expressions

were evaluated. How exactly did that happen?

Well. The rules are well known from simple

algebra. To evaluate a non-primitive expression, we

typically take the left most operator, subject to the rules of precedence.

We evaluate the operands of that operator. Typically left before right.

And we apply the operator to the operands. What about evaluating a name?

Well, that's evaluated by replacing it with the right hand side of its

definition. We applied these steps one by one, until

finally, the evaluation process results in a value.

And for the moment the value is just a number, later on, we also consider other

kinds of values. So let's see an example, here's the

evaluation of the arithmetic expression, two times pi times radius.

What's happening there, first thing we do is we look up the name pi and we get its

definition, 3.14159. Then we perform the arithmetic operation

on the left. Then we look up the radius evaluate radius

yielding ten, and finally, we perform the final multiplication, yielding the result.

Definitions can also have parameters. For instance, we can define a square

function by writing def square. Then comes the parameter x of type double,

and then comes the right hand side, x times x.

The right hand side refers to the parameter x.

And the p evaluation then would yield the expected square of two, would give four, a

square of five times four would give 81, We can, of course, also have,

parameterized definitions that refer to parameterized functions as in the sum of

squares function here, that takes two parameters, x and y of type double, and it

computes the square x and square y and sums them.

4:35

We've seen in the last slide that function parameters come with a type, and this type

is given after the parameter name and a colon in Scala.

So you would write x colon double y colon int.

You can also give the return type of a function that follows after the parameter

list. The permative types are written as in

Java, but they are capitalized. So, int for example, is a 32-bit integer,

double is a 64-bit, 40-point number, and Boolean represents a Boolean type bit

value true and false. Now, how are function applications

evaluated? In fact, they are evaluated in a way that

is very similar to operators. We first evaluate all function arguments

going from left to right. We then replace the function application

by the function's right hand side, and at the same time, we replace the former

parameters of the function by the actual arguments.

5:38

Let's see how this works in an example. We start with sum of squares of three and

two + two. The two + two gets rewritten to a value,

four, and we and, we have sum of squares three, four.

Then we take the definition of sum of squares, that was square of x plus square

of y, and we replace the application by this right hand side, where at the same

time we replace the parameters x and y by the actual arguments three and four.

We then repeat the process with the square applications.

The square of three becomes three times three.

The right turn side of square where three replaces the promidence.

We get an arithmetic simplification, and we do the same thing for the right hand

operators yielding the final value 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.

And that reduction can be expressed by a sequence of simple rewriting steps that

rewrite the expression term itself until it is a value, very similar to what you

would do in algebraic simplification. Simple as it is, this model is very

powerful. In fact it has been shown that it can

express every algorithm, so it's equivalent to what you would call a Turing

machine. This has been shown a long time before

functional programming, in fact, a long time before computers by a logician called

Alonzo Church in the lambda calculus. He did that in the thirties of the last

century. You might have come across lambda calculus

in theoretical computer science, and if you did, then you know how to make the

connection to this model. If you didn't, don't worry.

We won't need the theory for following the course.

What's important though is to know that number calculus as a model, and the

substitution model, can be applied only to expressions that do not have a side

effect. Now, what is a side effect?

A typical side effect would be an expression called.

C++ where c is a variable. And evaluating this expression means that

you return the old value of c, and at the same time you increment the value.

So, at the next time you return the value, you would return the value one larger than

before. Turns out there's no good way to represent

this evaluation sequence by a simple rewriting of this term.

You need something else, like store, where the current value of the variable is kept.

In other words the expression C++ has a side effect on the current value of the

variable. And that side effect cannot be expressed

by the substitution model. So one of the motivations for ruling out

side effects in function programming is that we can keep to the simple model of

evaluation. Once we have the substitution model,

another question comes up. Does every expression reduce to a value in

a finite number of steps? In fact, the answer is no.

Here's a counter example. Let's write simple function def loop int

equals loop. And that's then called, referred to the

name loop. What would happen.

Well, according to our model we have to de evaluate that name which happens by

replacing the name by its right hand side. The right hand side is looped again.

So we have reduced the name to itself and this can go on ad infinitum.

Another way to visualize this reduction sequence is by starting with the loop and

then reducing to the same term again. We've seen that the Scala interpreter

reduces function arguments to values before rewriting the function application.

That's not the only possible reduction strategy.

Alternatively, one could apply the function to unreduced arguments.

For instance, we could start with some of squares three, two plus two, and then go

on as follows. We keep the right hand side.

We don't reduce it to four and we simply pass it as an expression to the square

function. We then simplify the right hand side as

before. And then do the same thing again, pass the

expression, two plus two to this occurrence of square, that gives two plus

two twice in the multiplication. We then simplify the two further steps to

arrive at the final result, 25. Now you've seen two ways to evaluate the

same expression. The first evaluation strategy is known by

core by value. The second is known as core by name.

An important theorem, in fact, of lambda calculus is that both strategies reduce to

the same final value as long as the reduced expression consists of pure

functions and both evaluations terminate. Call-by-value has the advantage that every

function argument is evaluated only once. Call-by-name has the advantage that a

function argument is not evaluated at all if the corresponding parameter is not used

in the evaluation of the function's body. Here's a question, say you're given the

following function definition: Def test of x and y equals x times x.

For each of the following function applications, indicate which evaluation

strategy is fastest, by which we mean, has the fewest reduction steps.

That's for test of two three then test of three plus four eight.

Test seven two times four, then finally, test three plus four, two times four.

Let's see how we would answer this question.

I have the test function here and I have the calls that I want to compare here.

Let's start with the first one, test two, three.