Simplifying expressions

In Pascal programs we can simplify complex expressions by removing common sub-expressions and evaluating them separately. Instead of:

     writeln ( ( x + y ) * ( x + y ) ) ;
we would probably write:
     z := x + y ; writeln ( z * z ) ;
which is clearer and more efficient. Hope programs consist only of expressions and it's even more important to simplify them. We do this by using a qualified expression:
     let z == x + y in z * z ;
This looks like an assignment, but it isn't. == is read as `is defined as' and z is local to the expression following the in. If we write something like:
     let z == z + 1 in z * z ;
we're actually introducing a new variable z to use in the sub-expression z * z. It hides the original one in the sub-expression z + 1.

There's a second form of qualified expression for people who like to use variables first and define their meanings later. It looks like this:

     z * z where z == x + y ;
The result of the qualified expression is the same whether we define it using let or where. x + y is evaluated first, and its value is used in the main expression.

The qualifying expression will often be a function application that defines a data structure. If we want to name part of the structure we can use a pattern on the left-hand side of the == symbol:

     dec time12 : num -> num # num ;
     --- time12 ( s ) <= ( if h > 12 then h-12 else h, m ) where
                    ( h, m, s ) == time24 ( s ) ;
We'll use this construction most often when we write recursive functions that define tuples. Here's a typical example. Suppose we want to form a string of words from a sentence. For simplicity a word is taken to be any sequence of characters, and words are separated in the sentence by any number of blanks. The sentence and a single word will be of type list ( char ) and the final sequence of words a list ( list ( char ) ).

It's fairly straightforward to obtain the first word. Here's a function that does it:

     dec firsttry : list ( char ) -> list ( char ) ;
     --- firsttry ( nil )    <= nil ;
     --- firsttry ( c :: s ) <= if c = ' '
                             then nil
                             else c :: firsttry ( s ) ;
One of the nice features of Hope is that we can type in and print out any kind of value, so it's easy to check out the individual functions of our program separately. If we test firsttry we'll see:
     firsttry ( "You may hunt it with forks and Hope" ) ;
     "You" : list ( char )
But there's a problem here because we're going to need the rest of the sentence if we're to find the remaining words. We must arrange that that the function returns the remaining list as well as the first word. This is where tuples come in:
     dec firstword : list ( char ) -> list ( char ) # list ( char ) ;
     --- firstword ( nil )    <= ( nil, nil ) ;
     --- firstword ( c :: s ) <= if c = ' '
                              then ( nil, s )
                              else ( ( c :: w, r ) where
                                     ( w, r ) == firstword ( s ) ) ;
The qualified expression is parenthesised so it only applies to the expression after the else, otherwise we'll evaluate firstword recursively as long as the sentence is non-empty, even if it starts with a blank. This version of the function produces:
     firstword ( "Hope springs eternal ..." ) ;
     ( "Hope","springs eternal ..." ) : ( list ( char ) # list ( char ) )
We can use this to define a function to split the sentence into a list of its individual words:
     dec wordlist : list ( char ) -> list ( list ( char ) ) ;
     --- wordlist ( nil )    <= nil ;
     --- wordlist ( c :: s ) <= if c = ' '
                             then wordlist( s )
                             else ( w :: wordlist ( r ) where
                                  ( w, r ) == firstword ( c :: s ) ) ;
which we can test by typing an application at the terminal:
     wordlist ( "  While there's life there's Hope  " ) ;
     [ "While","there's","life","there's","Hope" ] : list ( list ( char ) )



Roger Bailey <rb@doc.ic.ac.uk>