Haskell: The Craft of Functional Programming Simon Thompson (c) Addison-Wesley, 1999. Chapter 10 Functions as values ^^^^^^^^^^^^^^^^^^^ > module Chapter10 where > import Prelude hiding (succ,curry,uncurry,flip) > import Pictures hiding (flipH,rotate,flipV,sideBySide,invertColour, > superimpose,printPicture) > import Chapter9 hiding (concat,map,filter,zipWith,and,foldr1,foldr, > doubleAll,flipV,sideBySide,getWord) > import qualified Chapter7 A fixity declaration for the forward composition operator, >.> > infixl 9 >.> Function-level definitions ^^^^^^^^^^^^^^^^^^^^^^^^^^ Revisiting the Picture example > rotate :: Picture -> Picture > rotate = flipV . flipH > flipH :: Picture -> Picture > flipH = reverse Function composition and forward composition ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A composition operator taking its arguments in the opposite order to `.'. > (>.>) :: (a -> b) -> (b -> c) -> (a -> c) > g >.> f = f . g Another definition of rotate using >.> > rotate' = flipH >.> flipV Functions as values and results ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Compose a function with itself: apply it twice, in other words. > twice :: (a -> a) -> (a -> a) > twice f = (f . f) > succ :: Int -> Int > succ n = n+1 We can generalize \texttt{twice} so that we pass a parameter giving the number of times the functional argument is to be composed with itself: > iter :: Int -> (a -> a) -> (a -> a) > iter n f > | n>0 = f . iter (n-1) f > | otherwise = id An alternative definition of iter: > iter' n f = foldr (.) id (replicate n f) Expressions defining functions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A function returning a function, namely the function to `add n to its argument'. > addNum :: Int -> (Int -> Int) > addNum n = addN > where > addN m = n+m Lambda notation ^^^^^^^^^^^^^^^ An alternative definition of addNum using a lambda expression. > addNum' :: Int -> (Int -> Int) > addNum' n = (\m -> n+m) The `plumbing' function: > comp2 :: (a -> b) -> (b -> b -> c) -> (a -> a -> c) > comp2 f g = (\x y -> g (f x) (f y)) Using the `plumbing' function > plumbingExample = comp2 sq add 3 4 > where > sq x = x*x > add y z = y+z Partial Application ^^^^^^^^^^^^^^^^^^^ The function multiply multiplies together two arguments. > multiply :: Int -> Int -> Int > multiply x y = x*y Double all elements of an integer list. > doubleAll :: [Int] -> [Int] > doubleAll = map (multiply 2) Another definition of addNum, using partial application to achieve the `function as result'. > addNum'' n m = n+m Revisiting the Pictures example, yet again. > flipV :: Picture -> Picture > flipV = map reverse > sideBySide :: Picture -> Picture -> Picture > sideBySide = zipWith (++) An example function of type (Int -> Int) -> Int > g :: (Int -> Int) -> Int > g h = (h 0) + (h 1) How many arguments do functions have? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Three examples from the text processing functions first seen in Chapter 7. > dropSpace = dropWhile (member whitespace) > dropWord = dropWhile (not . member whitespace) > getWord = takeWhile (not . member whitespace) Auxiliary definitions ... > member xs x = elem x xs Operator Sections ^^^^^^^^^^^^^^^^^ Example of a function defined using partial application and operator sections. > egFun :: [Int] -> [Int] > egFun = filter (>0) . map (+1) Revisiting the Picture example ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Some of the functions are already (re)defined in this script. Among the other functions mentioned were > invertColour :: Picture -> Picture > invertColour = map (map invert) > superimpose :: Picture -> Picture -> Picture > superimpose = zipWith (zipWith combineChar) The definition of combineChar is left as an exercise: it's a dummy definition here. > combineChar :: Char -> Char -> Char > combineChar = combineChar Printing a picture: uses putStr after a newline has been added at the end of every line and the lines are joined into a single string. > printPicture :: Picture -> IO () > printPicture = putStr . concat . map (++"\n") Further examples ^^^^^^^^^^^^^^^^ Revisiting earlier examples ... Double all integers in a list, > doubleAll' :: [Int] -> [Int] > doubleAll' = map (*2) get the even numbers in a list of integers, > getEvens :: [Int] -> [Int] > getEvens = filter ((==0).(`mod` 2)) get a word from the start of a string. > getWord' = getUntil (`elem` whitespace) Currying and uncurrying ^^^^^^^^^^^^^^^^^^^^^^^ An uncurried function to multiply together the two itegers in a pair. > multiplyUC :: (Int,Int) -> Int > multiplyUC (x,y) = x*y Turn an uncurried function into a curried version, > curry :: ((a,b) -> c) -> (a -> b -> c) > curry g x y = g (x,y) and vice versa. > uncurry :: (a -> b -> c) -> ((a,b) -> c) > uncurry f (x,y) = f x y Change the order of arguments of a two argument curried function. > flip :: (a -> b -> c) -> (b -> a -> c) > flip f x y = f y x Example: creating an index ^^^^^^^^^^^^^^^^^^^^^^^^^^ The basic type symonyms > type Doc = String > type Line = String > type Word = String The type of the top-level function > makeIndex :: Doc -> [ ([Int],Word) ] The top-level definition > makeIndex > = lines >.> -- Doc -> [Line] > numLines >.> -- [Line] -> [(Int,Line)] > allNumWords >.> -- [(Int,Line)] -> [(Int,Word)] > sortLs >.> -- [(Int,Word)] -> [(Int,Word)] > makeLists >.> -- [(Int,Word)] -> [([Int],Word)] > amalgamate >.> -- [([Int],Word)] -> [([Int],Word)] > shorten -- [([Int],Word)] -> [([Int],Word)] Implementing the component functions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Attach a number to each line. > numLines :: [Line] -> [ ( Int , Line ) ] > numLines linels > = zip [1 .. length linels] linels Associate each word with a line number > numWords :: ( Int , Line ) -> [ ( Int , Word ) ] > numWords (number , line) > = [ (number , word) | word <- Chapter7.splitWords line ] The definition uses splitWords from Chapter 7, modified to use a different version of whitespace. For this to take effect, need to make the modification in the Chapter7.lhs file. > whitespace :: String > whitespace = " \n\t;:.,\'\"!?()-" Apply numWords to each integer,line pair. > allNumWords :: [ ( Int , Line ) ] -> [ ( Int , Word ) ] > allNumWords = concat . map numWords The list must next be sorted by word order, and lists of lines on which a word appears be built. The ordering relation on pairs of numbers and words is given by > orderPair :: ( Int , Word ) -> ( Int , Word ) -> Bool > orderPair ( n1 , w1 ) ( n2 , w2 ) > = w1 < w2 || ( w1 == w2 && n1 < n2 ) Sorting the list using the orderPair ordering on pairs. > sortLs :: [ ( Int , Word ) ] -> [ ( Int , Word ) ] > sortLs [] = [] > sortLs (p:ps) > = sortLs smaller ++ [p] ++ sortLs larger > where > smaller = [ q | q<-ps , orderPair q p ] > larger = [ q | q<-ps , orderPair p q ] The entries for the same word need to be accumulated together. First each entry is converted to having a list of line numbers associated with it, thus > makeLists :: [ (Int,Word) ] -> [ ([Int],Word) ] > makeLists > = map mklis > where > mklis ( n , st ) = ( [n] , st ) After this, the lists associated with the same words are amalgamated. > amalgamate :: [ ([Int],Word) ] -> [ ([Int],Word) ] > amalgamate [] = [] > amalgamate [p] = [p] > amalgamate ((l1,w1):(l2,w2):rest) > | w1 /= w2 = (l1,w1) : amalgamate ((l2,w2):rest) > | otherwise = amalgamate ((l1++l2,w1):rest) Remove all the short words. > shorten :: [([Int],Word)] -> [([Int],Word)] > shorten > = filter sizer > where > sizer (nl,wd) = length wd > 3 Verification and general functions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ All the functions used in this section have been defined earlier.