These are some advanced notes extending the material presented this week. Don't worry about mastering this stuff now if you're already feeling saturated. But you will need to learn these things in the near future, so tackle them as soon as you're ready.

More on multivalues

A multivalue is a series of zero or more values. They are the result of evaluating expressions that consist of zero or more expressions, separated by commas, and enclosed in parentheses. So these expressions evaluate to multivalues:

(10, x + 2, x > 0)
(0, λx. x)

but so too do these:

(10)
()

When there's only a single expression, the surrounding parentheses are optional---though you may want them anyway for grouping, as in:

30 - (2 - 1)

In order to supply the (length-one) multivalue 1 as the second argument to the 30 - ... expression, you have to surround 2 - 1 by parentheses. If you just wrote:

30 - 2 - 1

it would be understood instead as:

(30 - 2) - 1

because the operator - implicitly associates to the left.

So officially I count ordinary single values as a special case of multivalue. Sometimes, though, I suspect I may say "multivalue" when what I mean is "multivalue that isn't a single value." If you catch me doing it, feel welcome to correct me.

We'll talk more about the length-zero multivalues later in the seminar.

Comments

Programming languages use lots of conventions for comments. Some languages have two kinds of comments: one sort starts at some special character or characters and extends to the end of the line, the other sort starts with some special opening characters and continues on, possibly for many lines, until reaching the special closing characters. Many Scheme implementations actually have three different syntaxes for comments.

In our toy language, we will use # until the end of the line for comments. This is also the convention in "shell scripts" and Python and a number of other languages.

In Scheme, one of the syntaxes for comments is that ; until the end of the line is a comment.

In Haskell, there are two syntaxes for comments. One goes from -- until the end of the line. The other starts with {- and continues until a matching -} is reached.

In OCaml, there are no until-the-end-of-the-line comments. The only comments start with (* and continue until a matching *) is reached.

I agree it's annoying that these conventions are so diverse. There are plenty other commenting conventions out there, too.

Matching function values

A function value doesn't have any structure---at least none that's visible to the pattern-matching system. You can only match against simple patterns like _ or the variable f.

When matching a λ-generated function value against a variable in a let- or letrec-construction, there's an alternative syntax that you may find more convenient. This:

let
  f match λx. φ;
  g match λ(x, y). χ
in ...

can equivalently be written like this:

let
  f x = φ;
  g (x, y) = χ
in ...

This is one of the few places where I'll indulge in the use of single =. Note that the left-hand sides of these bindings aren't function applications. They are a special syntax, that is interpreted exactly the same as the original expression displayed above.

This special syntax is only permitted in let- and letrec-constructions, not in case-constructions.

Pattern guards

In case contructions, it's sometimes useful to check not only whether a certain pattern matches, but also whether a certain boolean expression is true, typically where we want some variables in that expression to be bound by the relevant pattern. Thus, for example, if we wanted to count the number of odd numbers in a sequence, we could do this:

letrec
  count_odd xs = case xs of
                   [] then 0;
                   y & ys then case odd? y of
                                 'true then 1 + count_odd ys;
                                 'false then    count_odd ys
                               end
                 end
in count_odd

It's a bit cumbersome, though, to have the doubly-embedded case-constructions. We could simplify it a bit by replacing the inner case with and if ... then ... else ..., but that would still be a deeper structure than we might like. Pattern guards are an extra bit of syntax to make this easier to write. Using pattern guards, the previous expression could be written as:

letrec
  count_odd xs = case xs of
                   [] then 0;
                   y & ys when odd? y then 1 + count_odd ys;
                   _ & ys             then count_odd ys
                 end
in count_odd

If we get to the y & ys line in the pattern list, and the pattern-match succeeds, then we check the guard expression odd? y, with y bound to whatever part of xs matched the corresponding part of the pattern y & ys. If that boolean expression is 'true, then we continue to the right-hand side, after the then, just as usual. But if the boolean expression is 'false, then we treat the whole line as a failed match, and proceed on to the next line in the binding list, if any.

As-patterns

Sometimes it's useful to bind variables against overlapping parts of a structure. For instance, suppose I'm writing a pattern that is to be matched against multivalues like ([10, 20], 'true). And suppose I want to end up with ys bound to [10, 20], x bound to 10, and xs bound to [20]. Using the techniques introduced so far, I have two options. First, I could bind ys against [10, 20], and then initiate a second pattern-match to break that up into 10 and [20]. Like this:

case ([10, 20], 'true) of
  (ys, _) then case ys of
                 x & xs then ...;
                 ...
               end;
  ...
end

Alternatively, I could directly bind x against 10 and xs against [20]. But then I would have to re-cons them together again to get ys. Like this:

case ([10, 20], 'true) of
  (x & xs, _) then let
                     ys match x & xs
                   in ...;
  ...
end

Both of these strategies work. But they are a bit inefficient. I said you didn't really need to worry about efficiency in this seminar. But these are also a bit cumbersome to write. There's a special syntax that enables us to bind all three of ys, x, and xs in the desired way, despite the fact that they will be matching against overlapping, rather than discrete, parts of the value [10, 20]. The special syntax looks like this:

case ([10, 20], 'true) of
  ((x & xs) as ys, _) then ...
  ...
end

$ Syntax

Haskell has a useful bit of syntax that we will adopt. They use $ as an infix operator that has the same kind of effect as Russell & Whitehead's period. It is semantically inert, and only affects grouping. It enables you to avoid some parentheses in lots of situations. For example, if you want to check that a sequence xs is not empty, you'd express that like this:

not (empty? xs)

but using the $ syntax, you could instead write:

not $ empty? xs

with the same meaning. This may look funny at first, but you'll get used to it quickly. In an expression like this:

(not $ empty? xs) or p

of course it's only empty? xs that gets negated. The $'s effect stops when we get to the closing ).

Another way to think of $ is as not being semantically inert, but rather as being an infix operator that expresses function application. Normally function application is expressed just by concatenation, so that f x expresses the result of applying (the value of) f to (the value of) x. But f $ x expresses function application too. It's useful because it's parsed a bit differently than simple concatenation. This expression:

not empty? xs

is parsed as (the nonsensical):

(not empty?) xs

but:

not $ empty? xs

is parsed as:

not (empty? xs)

Some common functions

Function composition, which mathematicians write as fg, is defined as λ x. f (g x). This notion is one you'll commonly be encountering in functional programming, so it's handy to have a short and clear notation for it. Haskell expresses this relation using a period, like this: f . g. But we are using the period for other purposes, as in our λ-constructions; and even Haskell gets into some awkwardness because they use it in other ways too. Perhaps we could use a simple o as an infix composition operator? I'm not sure if that would be clear enough. For the time being, I'm electing to write this notion as comp, but as an infix expression, so we write: f comp g to mean λ x. f (g x). We may revisit this notational proposal later.

We've already come across the id function, namely λ x. x.

Other common functions are fst, which takes two arguments and returns the first of them; snd, which takes two arguments and returns the second of them; and swap, which takes two arguments and returns them both but with their positions swapped. A fourth function is dup, which takes one argument and returns it twice. These functions can be defined like this:

let
  fst (x, y) = x;
  snd (x, y) = y;
  swap (x, y) = (y, x);
  dup x = (x, x)
in (fst, snd, swap, dup)

Sections

OCaml and Haskell have a convenient bit of syntax for the common case where you want a function like this:

lambda x. 10 - x

or like this:

lambda x. x & ys

or like this:

lambda (x, y). x + y

They permit you to appreviate the first λ-expression as simply (10 - ). We know there's an argument missing, because the infix operator - demands two arguments, but we've only supplied one. So (10 - ) expresses a function that takes an argument x and evaluates to 10 - x. In other words, it expresses λx. 10 - x. Similarly, ( & ys) expresses a function that takes an argument x and evaluates to x & ys.

All of this only works with infix operators like -, & and +. You can't write (1 swap) or (swap 1) to mean λx. swap (1, x).

Can you guess what our shortcut for the last function will be? It's ( + ). That expresses a function that takes two arguments (x, y) and evaluates to x + y.

Wait a second, you say. Isn't that just what + does already? Why am I making a distinction between + and ( + )? The difference is that bare + without any parentheses is an infix operator that comes between its arguments. Whereas when we wrap it with parentheses, it loses its special infix syntax and then just behaves like a plain variable denoting a function, like swap. Thus whereas we write:

x + y

if we want to use ( + ), we have to instead write:

( + ) (x, y)

It may not be obvious now why this would ever be useful, but sometimes it will be.

All of these shorthands (10 - ), ( & ys) and ( + ) are called "sections". I don't know exactly why.

Confession: actually, what I described here diverges a bit from how OCaml and Haskell treat ( + ). They wouldn't really write ( + ) (x, y) like I did. Instead they'd write ( + ) x y. We will look at the difference between these next week.