Tip:
Highlight text to annotate it
X
The program we developed in the last session consisted of many small bits and
pieces. In this session, we are going to develop a
fundamental structuring mechanism based on blocks and lexical scoping that will help
us clean up our program, make it more modular and easier to use.
We're going to apply that mechanism to our previous square root function.
It's good, programming style, particularly in functional programming, to split up a
task into many small functions. But on the other hand, names like square
root iter improve and is good enough, they really matter for only the implementation
of square root not for its usage. Normally, we would not like users to
access these functions directly, so we want to avoid name space pollution where
they would see these names even though they're not suppose to be called directly
by them. One way we can achieve that is to put
these auxiliary functions inside the function square root.
So, let me show you that, that with the worksheet.
What we have here so far is, all these functions are separate and visible on the
top level. So, what I want to do now is I take my
definition of square root and I wrap it around the functions that are on the
inside here. So, I do it like that.
I reformat, and there what do we see? So, we see the function square root that
now contains square root iter is good enough and improved, as well as the return
value of square root. Return value here comes last.
What that means is that, now our program is much cleaner.
The only function that is seen from the outside is square root.
We still have very good names for the individual sub-steps, but those are
accessible only from inside this algorithm.
So, the way we did that was using a block. A block is delimited by braces and it
contains as number of definitions and at its last element an expression that
defines the return value of the result of that block.
So, here you see a simple example. You have a defi, definition of val x as of
the result of f of three and we return x times x.
Blocks are, themselves, expressions in Scala.
So, they can be used everywhere. Other expressions can be used, including
the right hand side of a function definition.
That's what we have seen in the eclipse example here, where this block here was
the return expression of the function square root.
Now an interesting aspect of blocks is how they affect visibility of identifiers in a
program. There are two simple rules.
The first is the definitions inside a block are only visible from within the
block, not from the outside. The second rule is that the definitions
from outside the block are visible in the block as long as they are not shadowed by
definitions of the same names inside the block.
So, that means that for instance, here the name f is visible in the block, it refers
to this outer block named f. But the name x here, refers to the inner
name x not the outer name x. The inner name x shadows the occurrence of
the outer name. Let's do an exercise.
I take a small variation of the program you've just seen.
The question is, what is the value of result in this program?
Possible answers are here. Think about it and pick one.
So, let's see how we would find the answer to that one.
If we look at the value of result, then what we see is that the first thing we do
is we compute the value of x to be f of three, f is this function here.
It adds one to it's parameter so that would be give x for x the value of four.
So then, the value of x times x would be sixteen, and we take that value, and add x
to it. What's the value of x here?
Well, we're now outside the block so the value of x here is no longer visible.
And the value we do see here is that this first definition, so that would be zero.
So, the answer of the whole expression, the result of the whole expression is
sixteen. So we've seen the definitions of outer
scopes are visible inside a block unless they're shadowed.
You can use that to simplify square root by eliminating redundant occurrences of
the x parameter, which means, everywhere, the same thing.
So, what you see here in the worksheet is that the x parameter that comes into
square root is duplicated here, here, and here, but it's never changed.
It's always passed as it is before. So, we can simply eliminate it, all these
occurrences here. And eliminate the corresponding parameter
in the application. And, we have the same version of square
root, but now it's much cleaner. We have avoided the redundancy of passing
the parameter x around everywhere by using the simple trick that the value of x is
actually visible inside all these nested functions.
So, that gives you another reason for nesting things, it's not just name space
control but it's also reusing outer definitions without passing them
explicitly in parameters. Okay, one thing we haven't mentioned so
far were semicolons, simply because so far we haven't seen them.
Even though in Java every statement would be terminated by a semicolon, in Scala
they are in most cases optional. You could have written val x is equals one
with a semicolon but most people would omit that.
The only situation where you really need a semicolon is if you want to put several
definitions or expressions on one line. Then, the semicolon is needed to separate
them. So for instance, here, you have a value
definition of y equals x plus one. And then a use of the variable y in a
subsequent expression and you need the semicolon here to separate the two.
Being able to commit semicolons at the end of lines is very co, convenient, but
there's one issue with it. How would you write an expression that
spans several lines? To see the problem, consider this
expression here, some long expression, then it continued on the second line with
some other expressions. That would actually be interpreted by the
Scala compiler as two expressions. The first expression is the some wrong
expression then, it would be the implied semicolon, and then comes plus with the
other expression. There are two ways to overcome this
problem. You could write the multiline expressions
in parenthesis, because semicolons are never inserted inside a pair of
parenthesis. So, you could write some wrong expression
plus some other expression as long as you put it in parenthesis and that's fine.
Or, the other way to do that is to write the operator on the first line as the last
word on the first line because this tells the Scala compiler that the expression is
not yet finished. So, if a line ends in infix operator the
Scala compiler will assume the next line forms part of the same expression.
So, to summarize what we have seen this week, we've seen elements of functional
programming in Scala, arithmetic and Boolean expressions, conditional
expressions, if then else, functions with recursion, nesting and lexical scope.
You've learned the difference between call by name and call by value evaluation
strategies. And I believe, most importantly, you've
learned a way to reason about program execution.
You can now reduce expressions using the substitution model.
That model will be an important tool for the coming sessions.