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 variable against a λ-generated function value 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] 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] 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] 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 `f` ○ `g`, 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. These functions can be defined like this:
let
fst (x, y) = x;
snd (x, y) = y;
swap (x, y) = (y, x)
in (fst, snd, swap)