GIML: Lesson Four

List processing and pattern matching

sum of a list

Consider the function sum which adds all the elements of a list. sum [2,3,1] = 2 + 3 + 1 = 6 There are two basic patterns for a list - that is there are two list constructors, :: and nil. The symbol :: is called cons, it has two components, nil is the empty list We can write equations for each of these constructors with completely general components. The empty list is easy - sum of all the elements in the empty list is zero. sum nil = 0 In the cons case we need to consider the value of sum(h::t). Where h is the head of the list - in this case an integer - and t is the tail of the list - i.e. the rest of the list. In constructing recursive functions we can assume that the function works for a case which is in some sense "simpler" than the original. This leap of faith becomes easier with practice. In this case we can assume that function sum works for t. We can use the value sum t on the right hand side of the definition. sum(h::t) = ??? sum(t); We are looking for an expression which is equal to sum(h::t) and we may use sum t in that expression. Clearly the difference between sum(h::t) and sum(t) is h. That is, to get from sum(t) to sum(h::t) simply add h fun sum nil = 0 | sum(h::t) = h + sum t;

appending (joining) two lists

The infix append function @ is already defined however we may derive its definition as follows The append operator joins two lists, for example [1,2,3] @ [4,5,6] = [1,2,3,4,5,6] The definition of an infix operator allows the left hand side to be written in infix. Given two parameters we have a choice when it comes to deciding how to recurse. If we choose to recurse on the second parameter the equations will be fun x @ nil = ?? | x @ (h::t) = ??; It turns out that this does not lead to a useful definition - we need to recurse on the first parameter, giving fun nil @ x = ?? | (h::t) @ x = ??; The first equation is easy, if we append nil to the front of x we just get x. The second equation is more difficult. The list h::t is to be put to the front of x. The result of this is h cons'ed onto the list made up of t and x. The resulting list will have h at the head followed by t joined onto x. We make use of the @ operator within its own definition. fun nil @ x = x | (h::t) @ x = h::(t @ x); Of course the @ operator is already defined. Note that the actual definition used is slightly different. Example: doublist Consider the function doublist which takes a list and doubles every element of it. doublist [5,3,1] = [10,6,2] Again we consider the two patterns nil and (h::t). The base case is nil doublist nil = nil A common mistake is to think doublist nil is 0. Just by looking at the type we can see that this would be nonsense. The output from doublist must be a list, not an integer. In considering the cons case an example may help. Imagine the execution of a particular list say doublist [5,3,1]. We rewrite [5,3,1] as 5::[3,1] and consider the second equation. doublist(5::[3,1]) = ??? doublist [3,1] Thanks to our faith in recursion we know that doublist[3,1] is in fact [6,2] and so we ask what do we do to [6,2] to get our required answer [10,6,2]. We answer "stick ten on the front". doublist(5::[3,1]) = 10::doublist [3,1] Returning to the general case with h and t instead of 5 and [3,1]: doublist(h::t) = 2*h :: doublist t

if .. then .. else ..

Sometimes pattern matching is not convenient. We may wish to compare values for example, in these cases the if .. then .. else .. structure is useful.

The expression if B then S1 else S2 tests the boolean expression B, it returns the value of S1 or the value of S2 depending on the value of B.

For example

if 1 = 0 then "I am the pope." else "someone else is the pope.";
Returns the string "someone else is the pope."

The following function "tells us" about a string s. A palimdrome is a word which is the same backwards as forwards.

fun pali s = if explode s = rev(explode s) then s ^ " is a palindrome."
					else s ^ " is not a palindrome.";
We can go further - the sentence is the same in both cases, except the substring "not " is missing in one case - this allows..
fun pal2 s = s^" is "^(if explode s = rev(explode s) then "" else "not ") ^
			"a palindrome.";
In some languages the else part is optional - that would make no sense in ML as the expression must return a value.


The @ operator

The append operator is defined in the file "/usr/local/software/nj-sml-93/src/boot/perv.sml" and is given as:
  infixr 5 :: @
  fun op @(x,nil) = x
    | op @(x,l) =
    let fun f(nil,l) = l
          | f([a],l) = a::l
        | f([a,b],l) = a::b::l
        | f([a,b,c],l) = a::b::c::l
        | f(a::b::c::d::r,l) = a::b::c::d::f(r,l)
     in f(x,l)

    end
This version may be shown to be equivalent to the simpler:
  infixr 5 :: @
  fun nil   @ l = l
  |   (h::t)@ l = h::(t@l)
but it will run faster.