------------------------------------------------------------------------------
--
--  Haskell: The Craft of Functional Programming, 3e
--  Simon Thompson
--  (c) Addison-Wesley, 1996-2011.
-- 
--  Chapter 10
--
-------------------------------------------------------------------------

-- Generalization: patterns of computation
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

module Craft.Chapter10 where

import Prelude hiding (map,filter,zipWith,foldr1,foldr,concat,and)
import Craft.Pictures hiding (flipV,beside)
import qualified Craft.Chapter7 as Chapter7
import Test.QuickCheck

-- Higher-order functions: functions as arguments
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

-- Mapping a function along a list.
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

map,map' :: (a -> b) -> [a] -> [b]

map' f xs = [ f x | x <- xs ]               -- (map.0)

map f []     = []                   -- (map.1)
map f (x:xs) = f x : map f xs               -- (map.2)

-- Examples using map.

-- Double all the elements of a list ...

doubleAll :: [Integer] -> [Integer]

doubleAll xs = map double xs
           where
           double x = 2*x
 
-- ... convert characters to their numeric codes ...

convertChrs :: [Char] -> [Int]
convertChrs xs = map fromEnum xs

-- ... flip a Picture in a vertical mirror.

flipV :: Picture -> Picture
flipV xs = map reverse xs


-- Modelling properties as functions
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

-- Is an integer even?

isEven :: Integer -> Bool
isEven n = (n `mod` 2 == 0)

-- Is a list sorted?

isSorted :: [Integer] -> Bool
isSorted xs = (xs == iSort xs)


-- Filtering -- the filter function
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

filter :: (a -> Bool) -> [a] -> [a]

filter p [] = []                -- (filter.1)
filter p (x:xs)
  | p x         = x : filter p xs       -- (filter.2)
  | otherwise   =     filter p xs       -- (filter.3)

-- A list comprehension also serves to define filter,

filter' p xs = [ x | x <- xs , p x ]        -- (filter.0)


-- Combining zip and map -- the zipWith function
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]

zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys
zipWith f  _      _     = []

beside :: Picture -> Picture -> Picture
beside pic1 pic2 = zipWith (++) pic1 pic2

--10.2
length' :: [a] -> Int
length' xs = sum $ map (\i -> 1) xs

prop_length' xs =
  length' xs == length xs

-- Folding and primitive recursion
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

-- Folding an operation into a non-empty list

foldr1 :: (a -> a -> a) -> [a] -> a

foldr1 f [x]    = x             -- (foldr1.1)
foldr1 f (x:xs) = f x (foldr1 f xs)     -- (foldr1.2)

-- Examples using foldr1

foldEx1 = foldr1 (+) [3,98,1]
foldEx2 = foldr1 (||) [False,True,False]
foldEx3 = foldr1 (++) ["Freak ", "Out" , "", "!"] 
foldEx4 = foldr1 min [6]
foldEx5 = foldr1 (*) [1 .. 6]

-- Folding into an arbitrary list: using a starting value on the empty list.

foldr f s []     = s                -- (foldr.1)
foldr f s (x:xs) = f x (foldr f s xs)       -- (foldr.2)

-- Concatenating a list using foldr.

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

-- Conjoining a list of Bool using foldr.

and :: [Bool] -> Bool
and bs = foldr (&&) True bs

-- Can define foldr1 using foldr:
--  foldr1 f (x:xs) = foldr f x xs          -- (foldr1.0)


-- Folding in general -- foldr again
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

-- The type of foldr is more general than you would initially expect...

foldr :: (a -> b -> b) -> b -> [a] -> b

rev :: [a] -> [a]
rev xs = foldr snoc [] xs

snoc :: a -> [a] -> [a]
snoc x xs = xs ++ [x]

-- Sorting a list using foldr

iSort :: [Integer] -> [Integer]
iSort xs = foldr Chapter7.ins [] xs

-- From the exercises: a mystery function ...

mystery xs = foldr (++) [] (map sing xs)
sing x     = [x]


-- Generalizing: splitting up lists
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

-- Getting the first word from the front of a String ...

getWord :: String -> String
getWord []    = []                  -- (getWord.1)
getWord (x:xs) 
  | elem x Chapter7.whitespace  = []            -- (getWord.2)
  | otherwise               = x : getWord xs    -- (getWord.3)

-- ... which generalizes to a function which gets items from the front of a list
-- until an item has the required property.

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

-- The original getWord function defined from getUntil

--  getWord xs 
--    = getUntil p xs
--      where 
--      p x = elem x whitespace

