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 ) )