

That is the Question.
and Equality Types.

Last lecture we talked about data. Now let's talk about functions.
But wait. We said that in functional languages functions are first-class data! So we need to be careful about that distinction.
Now what are the things we said you could do with first class data?
But what else can you do. In C, what can you do with an integer that you can't do with a function?
So, if you want the function which adds 1 to an integer, it's:
If you want to apply it to a number, just put it in the function position in an expression:
Now, most of the time we want to use functions many times, so we assign them a name.
Now, since most functions get named, and this syntax is a little cumbersome, SML provides a simpler declaration form for functions:
fun add1 n = n+1;

This form of declaration is not just syntactic sugar for the first form. There is an important difference in functionality (no pun intended). Consider this declaration:
val badfact = fn n => if (n <= 1) then 1 else n * (badfact (n-1));
It doesn't work. Why?
In contrast, a name declared using fun is available in the expression the
name is being assigned to:
fun fact n = if (n <= 1) then 1 else n * (fact (n-1));
To do this using a val definition, you must use the special recursive declaration,
val rec, as in:
val rec fact = fn n => if (n <= 1) then 1 else n * (fact (n-1));
The declaration using fun is just syntactic sugar for this declaration.

These two forms of recursive definition work for defining individual recursive functions. But what if you want to write a pair of mutually recursive functions like the following?
f(0) = 0
f(n) = g(2n)
g(1) = 1
g(n) = f(n div 3)
You can't write:
fun f(0) = 0 | f(n) = g (2 * n) fun g(1) = 1 | g(n) = f (n div 3)
Why Not?
Therefore, SML provides
and to allow mutually recursive definitions. You can tie any two (or more)
definitions of the same sort (val, fun, datatype, etc.)
together by excluding the sort indication from the later ones and replacing it
with and. So, we would define these two functions as:
fun f 0 = 0 | f n = g (2 * n) and g 1 = 1 | g n = f (n div 3)In general, mutual recursion should be avoided whenever possible as it is hard to read and comprehend. But sometimes it is the best solution.

Now, the definition of factorial above uses an if-then-else.
For the fibonacci function (here's a gift) you could do the same thing twice:
fun fib n = if (n = 0) then 0 else if (n = 1) then 1 else fib (n-1) + fib (n-2);
Or you could use the SML case statement:
fun fib2 n = case n of 0 => 0 | 1 => 1 | _ => fib2 (n-1) + fib2 (n-2);

The compiler is very careful to check the range of the type against the matches you provide. Look at these two functions:
fun booltest b = case b of false => "liar" | true => "saint";fun badinttest i = case i of 0 => "tiny" | 1 => "small";
The best policy is to make sure you always have a case for all possible matches.
fun inttest i = case i of 0 => "tiny" | 1 => "small" | _ => "out of range";

Now the designers of SML realized that an enormous amount of code in most
languages is written just to select between different cases of the input
arguments. So, they included syntactic sugar that weaves the case
statement directly into the fun declaration.
fun fib3 0 = 0 | fib3 1 = 1 | fib3 n = fib3 (n-1) + fib3 (n-2);

SML/NJ provides an extension of the Standard ML pattern syntax to allow a feature that is often quite useful. In particular, the syntax of patterns has been modified to allow ``or-patterns'' (sometimes called ``multi-headed patterns''). The basic syntax is:
(apat1 | ... | apatn)where the apati are atomic patterns. The other restriction is that the variables bound in each apati must be the same, and have the same type. A simple example is:
fun f ("y"|"yes") = true
| f _ = false;
which has the same meaning as:
fun f "y" = true | f "yes" = true | f _ = false;

Important Note:
It is crucial to make sure that you type the names of constants and constructors correctly.
fun booltest2 fase = "liar" | booltest2 true = "saint";fun inttest2 O = "tiny" % Thats an "Oh" not a "Zero" | inttest2 1 = "small" | inttest2 _ = "out of range";
Because the first case of each of these functions catches all cases of input, the subsequent cases will never be used and are thus redundant. The SML compiler will therefore issue a fatal error for this code.

Pattern matching is a very powerful tool in ML for shrinking the size of code. Suppose we want to write a function that will take a pair of integers and return a pair with each position incremented. We could write:
fun pairinc1 pr = ((#1 pr) + 1, (#2 pr) + 1);
But, the better solution is:
fun pairinc2 (a,b) = (a+1,b+1);
Now, if we want a version that will leave pairs that have a zero in either position alone, we just write:
fun pairinc3 (0,a) = (0,a) | pairinc3 (a,0) = (a,0) | pairinc3 (a,b) = (a+1,b+1);
This is a little awkward though. For this reason SML provides what are called layered patterns:
fun pairinc4 (pr as (0,a)) = pr | pairinc4 (pr as (a,0)) = pr | pairinc4 (a,b) = (a+1,b+1);
Or, even better:
fun pairinc5 (pr as (0,_)) = pr | pairinc5 (pr as (_,0)) = pr | pairinc5 (a,b) = (a+1,b+1);
We can compress this even more using or-patterns to produce:
fun pairinc6 (pr as ((0,_)|(_,0))) = pr | pairinc6 (a,b) = (a+1,b+1);

Pattern matching isn't actually specific to function definitions. It can also be
used in val declarations, as in:
val (a,b) = pairinc5 (2,3);
or
val pr as (c,d) = pairinc5 (4,10);

Now, so far all our functions have taken just a single parameter. Suppose we want to write a function that just adds two numbers together. We could give the two numbers to the function as a tuple, like:
fun add (x,y) = x + y;But we can also give them as two separate parameters as in:
fun add' x y = x + y;Now, the type of the first function is easy to understand:
val add = fn : int * int -> intBut, what does the second version's type mean?
val add' = fn : int -> int -> int
We said that when we call the function, as in:
add' 3 4;it's as though we had parenthesised it as:
(add' 3) 4;This seems to indicate that when you apply
add' to 3 it returns
a function which then gets applied to the 4. In fact that's just what
happens, and just what the type said. In types, the arrow associates to the
right, which means that the type reported before is equivalent to:
val add' = fn : int -> (int -> int)Which says that
add' takes an integer as a parameter and returns a
function which takes an integer and returns an integer.
This is really what's happening. I can prove it by just storing the result of
applying add' to 3.
val add3 = add' 3; add3 4;

This is a very powerful idea.
I should note that the fact that we can restrict our view to functions of one
argument is emphasized by the fact that the fn form for writing
unnamed functions only allows a single parameter. So, in that style we would
define add as:
val add'' = fn x => fn y => x + y;
Sometimes, when we want to emphasize that a function will mostly be used to
return another function we will mix the two styles. So, for instance, if we
mostly intended to use the add function to build custom adder functions
for given integers, we might right it like this:
fun add''' x = fn y => x + y;
It's important to realize that all three ways of declaring the add
function are equivalent.
By the way, the formal name for having a function of two parameters implemented instead as a higher-order function that takes a parameter and returns a function which takes the second parameter is called currying the function. It's named for Haskell Curry who was one of the inventors of the lambda-calculus which is the formal system functional languages are built on.

Here is a more interesting function that works on lists:
fun addUp nil = 0 | addUp (h::t) = h + (addUp t);Notice it's type:
Why is that?
Now, consider the following function:
fun length nil = 0 | length (h::t) = 1 + (length t);
What is it's type, what does that mean, and why is it different from the type of the last function?

ML will always report the most general type it can for a function. In
length the only constraint on the parameter is that it must be some kind
of list. We can get even more general than that. For instance:
fun id x = x;puts absolutely no constraints on its parameter. All it does is take it in and return it back. These operations can be applied to any first class type.
It's type is:
Finally, consider the function:
fun K x y = x;This takes two parameters tosses out the second and returns the first.

An interesting class of constraints is seen in the following two programs:
fun foo e lst = if length (e::lst) > 3 then true else false;
fun memb e nil = false | memb e (h::t) = if h = e then true else memb e t;
What are their types, and why are they different? What constraint does the second function place on the types of the element in the list that the first does not?

One additional use of and that isn't often discussed is that it allows
two definitions that are not necessarily mutually recursive to share type
information. For example, consider the following silly pair of definitions:
fun f m n = g (m * n) and g n = n / 2.0;

This page copyright ©2000 by Joshua S. Hodas. It was built on a Macintosh. Last rebuilt on Monday, January 31, 2000 | |
http://www.cs.hmc.edu/~hodas/courses/cs131/lectures/lecture03s.html | |