HigherOrderFunctions.hs

-- | Higher Order Functions
-- Examples to introduce and illustrate the power of higher order functions
-- Functional Programming course 2018.
-- Thomas Hallgren
--------------------------------------------------------------------------------
{-
This stared as a skeleton, the definitions were filled in
during the lecture.
-}


import Prelude hiding (map,filter,sum,product,concat,foldr,
                       takeWhile,dropWhile,lines)
import Data.Char(isSpace)
import Data.List(sort,group)

-- * Passing functions as arguments

-- | Using an existing function
evens = filter even [1..10]        -- [2,4,5,8,10]

-- | Defining a helper function
squares = map square [1..10]       -- [1,4,9,16,25,36,49,64,81,100]
  where square x = x*x

-- | Partial application
--haha = map take2 ["Hallo","Haskell"]
--  where take2 xs = take 2 xs

haha = map (take 2) ["Hallo","Haskell"]    -- ["Ha","Ha"]


-- wrong argument order?
--prefixes = map prefix [1..7]
--  where prefix n = take n "Haskell"

prefixes = map (flip take "Haskell") [1..7]
prefixes' = map (`take` "Haskell") [1..7]
-- ["H","Ha","Has","Hask","Haske","Haskel","Haskell"]

-- ** Sections (partial applications of infix operators)

m3 = map (*3) [1..10]         --  [3,6,9,12,15,18,21,24,27,30]

lt3 = filter (<3) [1..10]     --  [1,2]

gt3 = filter (3<) [1..10]     --  [4,5,6,7,8,9,10]

between = filter p [1..10]    --  [4,5]
  where p n = 3<n && n<6
  
-- ** Function composition
-- | removeSpaces "abc def \n ghi" = "abcdefghi"
removeSpaces s = filter (not . isSpace) s

-- ** Anonymous functions
squares' = map (\x->x*x) [1..10]

between' = filter (\n-> 3<n && n<6) [1..10]   -- [4,5]


-- * Curried functions: see also the slides

-- take :: Int -> [a] -> [a]
-- uncurry take :: (Int,[a]) -> [a]

example = map (uncurry take) [(2,"Haskell"),(3,"Function")]  -- ["Ha","Fun"]

-- * Defining higher order functions

-- ** How to define map and filter

map :: (a -> b) -> [a] -> [b]
map f []     = []
map f (x:xs) = f x:map f xs

filter :: (a -> Bool) -> [a] -> [a]
filter p []     = []
filter p (x:xs) | p x       = x:filter p xs
                | otherwise = filter p xs

-- ** Replacing many first-order functions with one higher-order function

-- Some examples of first order functions on lists

sum :: Num a => [a] -> a
sum []     = 0
sum (x:xs) = x+sum xs

product :: Num a => [a] -> a
product [] = 1
product (x:xs) = x * product xs

concat :: [[a]] -> [a]
concat [] = []
concat (xs:xss) = xs ++ concat xss

-- Factor out the differences from the common pattern

foldr :: (a->b->b) -> b -> [a] -> b
foldr op z []     = z
foldr op z (x:xs) = x `op` foldr op z xs

-- Now functions like the ones above can be simple one-line definition
sum' xs      = foldr (+) 0 xs
product' xs  = foldr (*) 1 xs
concat'  xss = foldr (++) [] xss
and' xs      = foldr (&&) True xs


-- ** More examples

weather = "June is warm\nJuly is warm\nJanuary is cold\n"

takeLine "" = ""
takeLine (c:cs) | c=='\n' = ""
                | otherwise = c:takeLine cs

takeWord "" = ""
takeWord (c:cs) | isSpace c = ""
                | otherwise = c:takeWord cs

-- Generalize takeLine and takeWord into takeWhile
takeWhile p [] = []
takeWhile p (c:cs) | p c       = c:takeWhile p cs
                   | otherwise = []


takeLine' s = takeWhile (/='\n') s
takeWord' s = takeWhile (not . isSpace) s

dropWhile p [] = []
dropWhile p (x:xs) | p x = dropWhile p xs
                   | otherwise = x:xs

dropLine s = drop 1 (dropWhile (/='\n') s)

-- lines weather == ["June is warm","July is warm","January is cold"]
lines :: String -> [String]
lines [] = []
lines s  = takeLine s:lines (dropLine s)

segments :: (a->Bool) -> [a] -> [[a]]
segments p [] = []
segments p xs = takeWhile p xs: segments p (drop 1 (dropWhile p xs))

-- * A larger example: counting words in a string
-- and produce nicely formatted output, most common word first,
-- written in "point-free style"

wordCounts :: String -> String
wordCounts = unlines
           . map (\(n,w)->w++": "++show n)
           . reverse
           . sort
           . map (\ws-> (length ws,head ws))
           . group
           . sort
           . words

-- wordCounts weather ==
--    "is: 3\nwarm: 2\ncold: 1\nJune: 1\nJuly: 1\nJanuary: 1\n"

-- Using function composition and eta reduction,
-- instead of:   f x = f1 (f2 (f3 x))
-- we can write: f = f1 . f2 . f3

Plain-text version of HigherOrderFunctions.hs | Valid HTML?