-- | 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 = 3x*x) [1..10]
between' = filter (\n-> 3 [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