Programming Languages and Paradigms

This is an easy problem to visualize. Suppose you have a list of at least two numbers:

(6 1 2 -3 8 4 -1 2 9 1 2 4)

Your task:

Write a function(2nd-max lon)that returns the value of thesecond-largestitem in the list of numberslon.

For example:

> (2nd-max '(6 1 2 -384 -1 291 2 4)) 8

You may solve this problem with or without recursion. Without recursion, you will need higher-order functions. With recursion, follow the data structure!

If you would like to use a function but aren't sure whether it is a Racket primitive, ask.

Bonus points to the **shortest** solution and to the
**most efficient** solution!

~~~~~

How many of you wrote recursive solutions? Functional ones? How short? How efficient? What ideas did you try?

What functions can help us?

If run-time efficiency is no concern, we can solve this in almost no code: sort the array from largest to smallest, then return the second element!

(define 2nd-max (lambda (lon) (second (sort lon >))))

Note that Racket has a primitive ** sort** function that
takes as arguments a list of values and a function for comparing
the items in the list.

With a list of numbers, demo with these comparators:

<(lambda (m n) (> (abs m) (abs n)))With a list of strings, demo with these comparators:

string<?(lambda (m n) (> (string-length m) (string-length n)))

If the thought of sorting the entire list to find two items makes you uneasy, then we might try another approach:

- Find the largest value in the list.
- Remove that item from the list.
- Find the largest value in the remaining list.

That may sound like it requires looping, but think back to the functional style of programming we saw in Session 6 when we computed total fuel for a set of space modules and in Session 7 when we computed total error for a set of predictions. In both cases, we used function calls and higher-order functions to replace loops.

We can use the Racket primitives ** apply** and

All we need to do is invoke this sequence functionally:

(define 2nd-max (lambda (lon) (apply max ; step 3 (remove ; step 2 (apply max lon) ; step 1 lon)))) ; step 2

... how do these compare to other solutions the class offered?

The first solution is O(*n* log *n*). The latter
is O(*n*), but makes three passes down the list. Can we
do better?

As we know, a list of numbers such as:

(6 1 2 -3 9 4 -1 2 8 1 2 4)has an inductive definition:

<list-of-numbers> ::= () | (<number> . <list-of-numbers>)

We know that the lists given to ** 2nd-max** have at
least two items, so maybe we need to define a new data type.
Let's think about the problem a bit first.

How might we approach this problem if we could write a loop? We would probably...

- Create two local variables,
`largest`and`2nd-largest`. - Initialize the variables using the first two items in the list.
- Look at each remaining item in the list to see if it is greater than either of the two variables and, if so, update the variables.

This seems easy enough, though we have to think about several
cases as we initialize and update the variables. Notice, though,
that after we peel the first two numbers off the front of the
list, we have a good old **list of numbers**, empty or a pair.
Our existing data type is what we need after all.

We can use structural recursion to start building a function that works this way:

(define 2nd-max (lambda (lon) (if (null? lon) ; return the answer ; handle a pair )))

But this isn't quite right... The recursive code will process the
list of numbers after the first two are removed, and it needs
access to the local variables `largest` and
`2nd-largest`. So, our recursive function has to be a
helper:

(define 2nd-max-tr(lambda (largest 2nd-largestlon) (if (null? lon) ; return the answer ; handle a pair )))

The *then* clause deals with the case where we have seen all
the numbers in the list. At this point, `2nd-largest` holds
the second largest value in the list, so we return it:

(define 2nd-max-tr (lambda (largest 2nd-largest lon) (if (null? lon)2nd-largest; handle a pair )))

The *else* clause handles a pair. It can do what our loop
would do in an imperative program: compare the next item in the
list, `(first lon)`, to `largest` and
`2nd-largest` and update them accordingly.

(define 2nd-max-tr (lambda (largest 2nd-largest lon) (if (null? lon) 2nd-largest(cond ((> (first lon) largest) (2nd-max-tr (first lon) largest (rest lon))) ((> (first lon) 2nd-largest) (2nd-max-tr largest (first lon) (rest lon))) (else (2nd-max-tr largest 2nd-largest (rest lon)))))))

Now all we have to do is write `2nd-max` as
an interface procedure
that kicks off the computation, initializing the variables with
the values of the first two items in the list:

(define 2nd-max (lambda (lon) (2nd-max-tr (max (first lon) (second lon)) (min (second lon) (first lon)) (rest (rest lon)))))

The result is a function that examines each item in the list
*exactly once* and finds the second-largest item. Our
recursive solution is longer but more efficient: it makes only
one pass over the list. We have traded programmer time for
execution time.

The helper function we wrote is a bit complicated. We handle
all three possibilities for `(first lon)` -- largest,
second largest, and neither -- with a `cond` expression.
This requires us to repeat the recursive call in all three cases.
Perhaps we can do better...

Let's use our interface procedure as an inspiration for something simpler:

(define 2nd-max-tr (lambda (largest 2nd-largest lon) (if (null? lon) 2nd-largest (2nd-max-trnew value of largest; -- handle (first lon)new value of second; -- in these expressionsnew value of lon; handle (rest lon) ))))

In an imperative solution, we would assign new values to the two
variables and branch to the top of the loop, which updates the
position in the array for the next pass. Doing the same thing
here, on the recursive call, simplifies the helper function.
After a little work (*think of it as a tournament with three
teams*), we arrive at:

(define 2nd-max-tr (lambda (largest 2nd-largest lon) (if (null? lon) 2nd-largest (2nd-max-tr(max largest (first lon))(max 2nd-largest (min largest (first lon)))(rest lon)))))

Notice that `2nd-max-tr` is **tail-recursive**, an idea
we learned about
last time.
It looks a bit like a loop -- and acts like one, too! Not only
does this solution make only pass over the loop, it uses one stack
frame, no matter how long the list is. And it's shorter than our
first recursive function.

I don't know about you, but I think this is a dandy little solution.

*Note: This section is just an outline of the things we
talked about in class. I'll fill it in as time permits*.

How might we compare these solutions? Here are some of the dimensions we might consider:

- length of the code
- complexity of the code
- space used at run-time
- time used at run-time
- ...
- complexity of the code
- ...
- familiarity

What happens if we are *not* given the precondition that
`2nd-max` always receives a list with at least two items?
How should our function act if it receives fewer than two items?

- We can guard against the "error" case in all solutions.
- When we have an interface procedure, we can guard against
the error once, and after that recur on the full definition
of a list of numbers. There is no need to test for
`(rest (rest lon))`ever again!

A few passing thoughts...

- These solutions remind us that there is a difference between reading code and writing it.
- It is usually better to get a solution down in code and then refactor it into something better than to try to write a perfect solution from the outset.
- Local variables might be nice in some of our more complex functions. They are coming soon! But notice how we use functions and parameter passing to simulate local variables. That's a hint for how local variables work.

First, we learned a new language, using that process to learn
**a new way to think about languages**: in terms of primitives,
means of combination, and means of abstraction.

Then we used the new language to learn **a new style of
programming**: functional programming. It uses functions,
both ordinary and higher-order, to solve problems.

Then we used the new language and style to learn **patterns of
recursive programming**. These patterns are especially useful
for processing inductive data types.

Now we are ready to begin moving into a new phase of the course,
in which we apply our new skills to learn **how our programming
languages work**.

Next session, and for most of the rest of the course, we will use what we have learned thus far about functional and recursive programming to explore issues in programming languages and their paradigms. That is a big space to work in. Let's start in one small corner of the programming languages world: where variables get their values from.

A property is **static** when its value can be determined by
looking at the text of a program. A property is **dynamic**
when the program must be executed in order to determine the
property. We can use static properties of a program to detect
errors and to improve program performance at interpretation or
compile time.

We have already discussed one property of variables this semester:
their *data type*. In Ada, the data type of a variable is
static. In Java, it is static, with a twist allowed by the
substitutability of objects of different classes. In Racket, the
data type of a variable is dynamic -- though we typically write
our code with a specific set of values and operations in mind.

In this session, we will begin to explore some of the static properties that variables can have.

How can recursive programming help us explore language features? It turns out that the syntax of a programming language is defined inductively!

The definitions of Python, Java, and Racket are quite large. Many of the features of these languages are syntactic sugar, added to make programmers' lives easier. But we don't need all of those features to understand how the languages work.

Much of our work the rest of the semester will use small languages with essential features as a way to study how languages work. Today we introduce such a language. We will use a family of similar languages to study different language features throughout the course.

If our goal is to explore where variables get their values from, what features does out little language need? Think about how function calls work in Racket and other languages. They require:

- a way to define a function that has a formal parameter
- a way to call a function, which binds a value to the formal parameter
- a way to use a variable in an expression

Today's little language has just these features:

<exp> ::= <varref> | (lambda (<var>) <exp>) | (<exp> <exp>)

This little language provides the bare minimum: an expression in
the language is either a variable reference, a function of one
parameter, or an application of a function to one argument. This
is all we need to study the topic *du jour*, which is the
static property of **free** and **bound** variables.

Quick Exercise: Earlier, we discussed the three things that every programming language has. Which of these features does our little language have? Which features is it missing?

Quick Answer: This language does not have primitive values of all sorts you are used to; its only primitives are variable references. Its means of combination is a subset of Racket's parenthesized operations. The language has procedural abstraction vialambda. It has naming abstractions, too: a value is associated with a formal parameter whenever we call a function.

This language is Racket-like in its use of parentheses and its
use of `lambda` to define a nameless function, but it
is universal in the features it provides. All languages you
know and love have the same three features, plus many more. We
will add more features to this language as time goes by, always
with an eye to how our language processors would manipulate
programs written in the language.

As you know, a variable can have a value or not have a value.
Of course, in some languages, such as Java, every variable has a
value, even if the program does not initialize it. For example,
a Java integer defaults to 0, and object variables default to
`null`.

Unfortunately, `null` isn't really a value at all. Many of
you encounter "null pointer exceptions" at run-time as you learn
how to write Java programs. These exceptions point out that the
variable in question *doesn't* really have a value, and as
a result the program cannot proceed.

On top of this idea of a variable *having* a value is the
idea of how the variable *receives* its value. If a
variable has the same name as the formal parameter on a method,
then we know that the variable is "bound" to that declaration,
and that the value of the variable will be the value passed as
the corresponding argument to the function.

int sumOfSquares( int m, int n ) { // m and n are bound to formal parameters return m*m + n*n; }

Let's define "boundedness" and some related ideas that apply to all programming language:

- A variable
**is bound**or**occurs bound**in an expression if it refers to the formal parameter in the expression. - A variable
**is free**or**occurs free**in an expression if it is not bound. - A variable that is bound by any formal parameter is said
to be
**lexically bound**or**statically bound**. We can determine its binding by reading the function. - A variable that is not statically bound
*must*be bound to something*dynamically*, that is, at run time, in order to be used. We say that such variables are**globally bound**. In Racket, all language primitives are bound dynamically.

(Can a variable occur and not be used? --*forward reference to Session 13*) - A function definition that contains no free variables is
called a
**combinator**. A combinator does not depend on any system primitives.

In Racket and our
little language,
above, `lambda` expressions define functions and so are the
source of boundedness.

Now let's see some examples from our little language that illustrate these concepts:

- In this expression,
(lambda (z) x)

`x`occurs free in the body of the`lambda`.`z`is a parameter, not a variable reference.`z`does not occur in the body, so it is*neither bound nor free*. - In this expression,
((lambda (x) x) y)

`x`is bound and`y`is free. - We can
*capture*that`y`by enclosing the expression in another`lambda`expression that has`y`as its formal parameter:(lambda (y) ((lambda (x) x) y) )

- The expression:
(lambda (x) x)

always has the same meaning: it returns the value of its argument unchanged. This function is called the**identity function**. It is also a combinator. It does not depend on any free variables, so its meaning is independent of any run-time concerns.

You may ask,*Of what use is the identity function*? In order to discover the answer, ask yourself: Where else we have used identity values in our programs? - This expression:
(lambda (f x) (f (f x)))

is also a combinator. It does not have the same value in all contexts, because it uses a function passed as an argument,`f`, to determine its value. Yet it is entirely independent of any run-time concerns, as it makes no reference to any system primitives. This sort of function is an important tool in functional programs, as it uses higher-order function to build programs.

We often write combinators in Racket. For example, the
`compose` function,
which implements the sort of function composition you learn
about in algebra, is a combinator:

(define compose (lambda (f g) (lambda (x) (f (g x)))))

However, some of the functions you may think of as having only
bound variables have free variables. This Racket function
**is not** a combinator:

(define sum-of-applications (lambda (f x y) (+ (f x) (f y))))

Why? Because `+` is a free variable! Remember: Racket
functions are bound to names in just the same way as any other
value. You can verify this for yourself by evaluating:

> (let ((+ *)) ((lambda (f x y) (+ (f x) (f y))) add1 1 4)) 10

One note before we proceed. We will be writing programs to
process programs our little language. The data type we will be
processing is the grammar of the little language. That grammar
uses Racket list notation, but that is an implementation detail.
Calls to `car`, `cdr`, and `cons` will
litter our code with implementation details. They will also
obscure what our code means.

Soon we will write functions to access the parts of the expressions in our data type. These functions will allow us to

- test whether an expression is a variable reference, a function, or an application, and
- extract the parts of functions and applications.

We will read more about these next time.

- Reading -- Review these notes, especially the sections beginning at the static properties of variables. We will quickly review a couple of ideas in this section next time and move on to writing programs that explore these ideas.
- Homework 5 is available and due next time.
- Quiz 2 will be one week from next Tuesday, on March 7.