home *** CD-ROM | disk | FTP | other *** search
-
-
- Introduction to Gofer 10. INCREASING YOUR POWER OF EXPRESSION
-
-
- 10. INCREASING YOUR POWER OF EXPRESSION
-
- This section describes a number of useful extensions to the basic range
- of expressions used in the previous sections. None of these add any
- extra computational power to Gofer -- anything that can be done with
- these constructs could also be done with the constructs already
- described. They are however included in Gofer because they allow many
- expressions and function definitions to be written more clearly and
- concisely than the equivalent expressions without these notations.
-
- 10.1 Arithmetic sequences
- -------------------------
- A number of useful lists can be generated using the notation of
- arithmetic sequences (so named because of their similarity to
- arithmetic progressions in mathematics). The following list summarises
- the four forms of sequence expression that can be used in Gofer,
- together with their translation using the standard functions enumFrom,
- enumFromTo, enumFromThen and enumFromThenTo:
-
- [ n .. ] enumFrom n
-
- Produces the (potentially infinite) list of values
- starting with the value of n and increasing in
- single steps.
-
- e.g. [1..] = [1, 2, 3, 4, 5, 6, 7, 8, 9, etc...
-
- [ n .. m ] enumFromTo n m
-
- Produces the list of elements from n upto and
- including m in single steps. If m is less than n
- then the list is empty.
-
- e.g. [-3..3] = [-3, -2, -1, 0, 1, 2, 3]
- [1..1] = [1]
- [9..0] = []
-
- [ n, m .. ] enumFromThen n m
-
- Produces the (potentially infinite) list of values
- whose first two elements are given by the values n
- and m. If m is greater than n then the following
- elements of the list are increasing in steps of
- the same size. A similar result is obtained if m
- is less than n in which case the elements of
- [n,m..] will be decreasing. If n and m are equal
- then [n,m..] is an infinite list in which each
- element is equal to n.
-
- e.g. [1,3..] = [1, 3, 5, 7, 9, 11, 13, etc...
- [0,0..] = [0, 0, 0, 0, 0, 0, 0, etc...
- [5,4..] = [5, 4, 3, 2, 1, 0, -1, etc...
-
- [ n, n' .. m ] enumFromThenTo n n' m
-
- Produces the list of elements from [n,n'..] upto
-
-
- 37
-
-
-
-
- Introduction to Gofer 10.1 Arithmetic sequences
-
-
- the limit value m. If m is less than n and
- [n,n'..] is increasing, or m is greater than n and
- [n,n'..] is decreasing then resulting list will be
- empty.
-
- e.g. [1,3..12] = [1, 3, 5, 7, 9, 11]
- [0,0..10] = [0, 0, 0, 0, 0, 0, 0, etc...
- [5,4..1] = [5, 4, 3, 2, 1]
-
- In the standard prelude, the functions enumFrom, enumFromTo,
- enumFromThen and enumFromThenTo are overloaded and may also be used to
- enumerate lists of characters or floating point values:
-
- ? ['0'..'9'] ++ ['A'..'Z']
- 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
- (397 reductions, 542 cells)
-
- ? [1.2, 1.35 .. 2.00]
- [1.2, 1.35, 1.5, 1.65, 1.8, 1.95]
- (56 reductions, 133 cells)
-
- ?
-
- Arithmetic sequences such as those described above play the same role
- in functional programming languages as the iterative `for' constructs
- in traditional imperative languages. A good example of this is the
- example in section 4 used to calculate the sum of the integers from 1
- upto 10 -- "sum [1..10]". An equivalent program in an imperative
- language might look something like (especially if you think of C!):
-
- int i;
- int total=0;
- for (i=1; i<=10; i++)
- total = total + i;
- return total;
-
- The advantages of the functional notation in this case are clear:
-
- o It is more compact.
-
- o It separates the task of generating the sequence of integers
- [1..10] from the task of finding their sum.
-
- o It does not require the declaration or use of auxiliary variables
- such as "i" and "total" in the above.
-
-
- 10.2 List comprehensions
- -------------------------
- List comprehensions provide another very powerful and compact notation
- for describing certain kinds of list expression. The basic form of a
- list comprehension is:
-
- [ <expr> | <qualifiers> ]
-
- There are three kinds of qualifier that can be used in Gofer:
-
-
- 38
-
-
-
-
- Introduction to Gofer 10.2 List comprehensions
-
-
- o Generators: A qualifier of the form pat<-exp is used to extract
- each element that matches the pattern pat from the list exp in the
- order that they elements appear in that list. A simple example of
- this is the expression [x*x | x<-[1..10]] which denotes the list
- of the squares of the integers between 1 and 10 inclusive and
- evaluates to [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] as expected.
-
- Formally, we can define the meaning of a list comprehension with a
- single generator by the equation:
-
- [ e | pat <- exp ] = loop exp
- where loop [] = []
- loop (pat:xs) = e : loop xs
- loop (_:xs) = loop xs
-
- If pat is an irrefutable pattern (for example, a variable) then
- this is equivalent to:
-
- [ e | pat <- exp ] = map f exp
- where f pat = e
-
- The full definition is needed for those cases where the pattern
- pat may not match all of the elements in the list exp. This is
- the case in expressions such as [ y | (3,y)<-[(1,2),(3,4),(5,6)] ]
- which evaluates to the singleton list [4].
-
- o Filters: A boolean valued expression may also be used as a
- qualifier in which case it is often called a filter. We can
- define the meaning of a list comprehension with a single filter by
- the equation:
-
- [ e | condition ] = if condition then [e] else []
-
- Whilst this form of list comprehension is occasionally useful as
- it stands, it is more common to use filters in conjunction with
- generators as described below.
-
- o Local definitions: A qualifier of the form pat=expr can be used to
- introduce a local definition within a list comprehension. Its
- meaning can be defined formally using the equation:
-
- [ e | pat = exp ] = [ let pat=exp in e ]
-
- As in the case of filters, local definitions are more commonly
- used within lists of more than one qualifier as described below.
- Particular care should be taken to distinguish a filter of the
- form pat==expr from a local definition of the form pat=expr.
-
- [ASIDE: I originally suggested this form of qualifier in a message
- sent to the Haskell mailing list, only to discover that a similar
- (and more comprehensive) suggestion had been made by Kevin Hammond
- almost a year earlier. There was a certain amount of controversy
- surrounding the choice of an appropriate syntax and semantics for
- the construct and consequently, this feature is not currently part
- of the Haskell standard. The syntax and semantics above is
- implemented by Gofer in the hope that it will give functional
-
-
- 39
-
-
-
-
- Introduction to Gofer 10.2 List comprehensions
-
-
- programmers an opportunity to experiment with this facility in
- their own programs.]
-
- The real power of this notation is that it is possible to use several
- qualifiers, separated by commas on the right of the vertical bar `|'
- symbol in a list comprehension. Formally, if qs1 and qs2 are two such
- lists of qualifiers, then we can define the meaning of multiple
- qualifiers using:
-
- [ e | qs1, qs2 ] = concat [ [ e | qs2 ] | qs1 ]
-
- The following examples illustrate how this definition works in
- practice:
-
- o Variables generated by later qualifiers vary more quickly than
- those generated by earlier qualifiers:
-
- ? [ (x,y) | x<-[1..3], y<-[1..2] ]
- [(1,1), (1,2), (2,1), (2,2), (3,1), (3,2)]
- (107 reductions, 246 cells)
- ?
-
- o Later qualifiers may use the values generated by earlier ones:
-
- ? [ (x,y) | x<-[1..3], y<-[1..x]]
- [(1,1), (2,1), (2,2), (3,1), (3,2), (3,3)]
- (107 reductions, 246 cells)
-
- ? [ x | x<-[1..10], even x ]
- [2, 4, 6, 8, 10]
- (108 reductions, 171 cells)
- ?
-
- o Variables defined in later qualifiers hide those introduced by
- earlier ones. The following expressions are valid list
- comprehensions, but this style of definition in which names are
- reused can result in programs which are difficult to understand,
- and is not recommended:
-
- ? [ x | x<-[[1,2],[3,4]], x<-x ]
- [1, 2, 3, 4]
- (18 reductions, 53 cells)
-
- ? [ x | x<-[1,2], x<-[3,4] ]
- [3, 4, 3, 4]
- (18 reductions, 53 cells)
- ?
-
- o Changing the order of qualifiers has a direct effect on
- efficiency. The following two examples produce the same result,
- but the first uses more reductions and cells because it repeats
- the evaluation of "even x" for each possible value of "y".
-
- ? [ (x,y) | x<-[1..3], y<-[1..2], even x ]
- [(2,1), (2,2)]
- (110 reductions, 186 cells)
-
-
- 40
-
-
-
-
- Introduction to Gofer 10.2 List comprehensions
-
-
- ? [ (x,y) | x<-[1..3], even x, y<-[1..2] ]
- [(2,1), (2,2)]
- (62 reductions, 118 cells)
- ?
-
- The following example illustrates a similar kind of behaviour with
- local definitions; in the first case the expression "fact x" is
- evaluated twice for each possible value of "x", whilst the second
- expression uses a local definition to ensure that the evaluation
- is not repeated:
-
- ? [ fact x + y | x<-[1..3], y<-[1..2] ]
- [2, 3, 3, 4, 7, 8]
- (246 reductions, 398 cells)
-
- ? [ factx + y | x<-[1..3], factx = fact x, y<-[1..2] ]
- [2, 3, 3, 4, 7, 8]
- (173 reductions, 294 cells)
- ?
-
-
- 10.3 Lambda expressions
- ------------------------
- In addition to named function definitions, Gofer also allows the
- definition and use of unnamed functions using a `lambda expression' of
- the form:
-
- \ <atomic patterns> -> <expr>
-
- [ASIDE: This is a slight generalisation of the form of lambda
- expression used in most theoretical treatments of functional
- programming and dating back to the pioneering work of logicians
- including Church, Curry and Haskell, from whom the programming language
- takes its name. The `\' character used at the beginning of a Gofer
- lambda expression has been chosen for its resemblance to the greek
- letter lambda that might be used if the standard character set were a
- little larger.]
-
- This expression denotes a function taking a number of parameters (one
- for each pattern) and producing the result specified by the expression
- to the right of the -> symbol. For example, (\x->x*x) represents the
- function which takes a single integer argument `x' and produces the
- square of that number as its result. Another example is the the lambda
- expression (\x y->x+y) which takes two integer arguments and outputs
- their sum; this expression is in fact equivalent to the (+) operator:
-
- ? (\x y->x+y) 2 3
- 5
- (3 reductions, 7 cells)
- ?
-
- A lambda expression of the form illustrated above is equivalent to the
- following expression using a local definition:
-
- (let newName <atomic patterns> = <expr> in newName)
-
-
-
- 41
-
-
-
-
- Introduction to Gofer 10.3 Lambda expressions
-
-
- where "newName" is a new variable name, chosen to avoid conflicts with
- other variables that are already in use. This name will be printed if
- you enter an expression involving a lambda expression without supplying
- the full number of parameters for that function:
-
- ? (\x y -> x+y) 42
- v117 42
- (2 reductions, 14 cells)
- ?
-
- Lambda expressions are particularly useful for certain styles of
- functional programming; an example of this is the continuation-based
- approach to I/O described in section 12.
-
-
- 10.4 Case expressions
- ---------------------
- A case expression can be used to evaluate an expression and, depending
- on the result, return one of a number of possible values. As such,
- case statements are a straightforward generalisation of conditional
- expressions. Indeed, an expression of the form "if e then t else f" is
- in fact equivalent to the case expression:
-
- case e of
- True -> t
- False -> f
-
- In general, a case expression takes the form "case exp of alts" where
- exp is the expression to be evaluated and alts is a list of
- alternatives, each of which is of the form:
-
- pat -> rhs for a simple alternative
-
- or: pat | condition1 -> rhs1 using guard expressions as
- | condition2 -> rhs2 described in section 9.2 for
- . function definitions
- .
- | conditionn -> rhsn
-
- In Gofer, a case expression of the form case e of alts is implemented
- by choosing a new function name "newName" as in the previous section
- and using the alternatives in alts to construct an appropriate
- definition for this function (essentially by replacing each `->' symbol
- with a `=' symbol). The complete case expression is then treated as
- being equivalent to the expression "newName e". A simple example of
- this is the "scanl" function whose definition in the standard prelude:
-
- scanl f q xs = q : (case xs of
- [] -> []
- x:xs -> scanl f (f q x) xs)
-
- is equivalent to:
-
- scanl f q xs = q : scanl' xs
- where scanl' [] = []
- scanl' (x:xs) = scanl f (f q x) xs
-
-
- 42
-
-
-
-
- Introduction to Gofer 10.4 Case expressions
-
-
- This latter form is precisely the definition used in [1] (but using the
- name "scan" where Gofer uses "scanl").
-
- Evaluating a case expression in which none of the alternatives match
- the value of the discriminant results in an error such as the
- following:
-
- ? case [1,2] of [] -> "empty list"
- {v117 [1, 2]}
- (6 reductions, 31 cells)
- ?
-
- The function name "v117" which appears here is the name of the function
- which is used internally by Gofer to implement the case expression
- whilst the expression "[1, 2]" gives the discriminant value which could
- not be matched.
-
- By combining case expressions with the lambda expressions introduced in
- the previous section, any function declaration can be translated into a
- single equation of the form <functionName> = <expr>. For example, the
- standard function "map" whose definition is usually written as:
-
- map f [] = []
- map f (x:xs) = f x : map f xs
-
- can also be defined by the equation:
-
- map = \f xs -> case xs of
- [] -> []
- (y:ys) -> f y : map f ys
-
- This kind of translation is used in the implementation of many
- functional programming languages, including Gofer. See Simon Peyton
- Jones book [2] for more details of this.
-
-
- 10.5 Operator sections
- ----------------------
- As we have seen, most functions in Gofer taking more than one argument
- are treated as a function of a single argument, whose result is a
- function which can then be applied to the remaining arguments. Thus
- "(+) 1" denotes the function which takes an integer argument "n" and
- returns the integer value "1+n". Functions of this kind involving
- operator symbols are sufficiently common that Gofer provides a special
- syntax for them. Using e to denote an atomic expression and the symbol
- "*" to represent an arbitrary infix operator, there are functions (e *)
- and (* e), known as `sections of the operator (*)' defined by:
-
- (e *) x = e * x
- (* e) x = x * e
-
- or, using lambda expressions as introduced in section 10.3:
-
- (e *) = \x -> e * x
- (* e) = \x -> x * e
-
-
-
- 43
-
-
-
-
- Introduction to Gofer 10.5 Operator sections
-
-
- For example: (1+) is the successor function which returns the value
- of its argument plus 1,
- (1.0/) is the reciprocal function,
- (/2) is the halving function,
- (:[]) is the function which maps any value to the
- singleton list containing that element.
-
- In Gofer, the expressions "(e *)" and "(* e)" are actually treated as
- abbreviations for "(*) e" and "flip (*) e" respectively, where "flip"
- is the function defined by:
-
- flip :: (a -> b -> c) -> b -> a -> c
- flip f x y = f y x
-
- There is an important special case which occurs with an expression of
- the form (- e); this is interpreted as "negate e" and not as the
- section which subtracts the value of "e" from its argument. The latter
- function can be written as the section (+ (- e)) or as "subtract e"
- where "subtract" is the function defined in the standard prelude using:
-
- subtract = flip (-)
-
-
- 10.6 Explicitly typed expressions
- ----------------------------------
- As described in section 9.12, it is often useful to be able to declare
- the type of a variable defined in a function or pattern binding. For
- much the same reasons, Gofer allows expressions of the form:
-
- <expr> :: <type>
-
- so that the type of an expression can be specified explicitly. Note
- that the :t command can be used to find the type of a particular
- expression that is inferred by Gofer:
-
- ? :t \x -> [x]
- \x -> [x] :: a -> [a]
-
- ? :t sum . map length
- sum . map length :: [[a]] -> Int
-
- ?
-
- The types inferred in each case can be modified by including explicit
- types in these expressions:
-
- ? :t (\x -> [x]) :: Char -> String
- \x -> [x] :: Char -> String
-
- ? :t sum . map (length :: String -> Int)
- sum . map length :: [String] -> Int
-
- ?
-
- Note that an error occurs if the type declared in an explicitly typed
- expression is not compatible with the type inferred by Gofer:
-
-
- 44
-
-
-
-
- Introduction to Gofer 10.6 Explicitly typed expressions
-
-
- ? :t (\x -> [x]) :: Int -> a
- ERROR: Declared type too general
- *** Expression : \x -> [x]
- *** Declared type : Int -> a
- *** Inferred type : Int -> [Int]
-
- ?
-
- Explicitly typed expressions are most commonly used together with
- overloaded functions and values as described in section 14.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 45
-
-
-