-- First class functions
-- Anonymous functions
h1 x = x + 4
h2 = \ x -> x + 4
-- nested functions
map1 (g,u) =
let f [] = []
f (x:xs) = (g x) : (f xs)
in f u
{-
This acts similarly to other nested declarations:
Parameters and local variables of outer functions are
visible within inner functions (using lexical scoping rules).
Purpose: localize scope of nested functions, and avoid the
need to pass auxiliary parameters defined in outer scopes.
Semantics of a function definition now depend on values of
function’s free variables .
-}
-- FUNCTIONS AS RETURN VALUES
map2 g =
let f [] = []
f (x:xs) = (g x) : (f xs)
in f
{-
In fact, we can simplify still further by rewriting map to
return the nested function "f"
Now we need a slightly different calling convention, passing
the two arguments separately:
-}
inc x = x+1
w1 = ((map2 inc) [1,2,3])
-- yields [2;3;4]
-- But now we can also choose to pass just one argument at a time
minc = map2 inc
w2 = minc [1,2,3]
w3 = minc [4,5,6]
-- Curried functions take multiple arguments
-- but we can apply the function to fewer arguments
map3 g u =
let f [] = []
f (x:xs) = (g x): (f xs)
in f u
w4 = map3 inc [2,5,7]
-- function application associates to the left, so
w5 = (map3 inc) [4,7,8]
{- Notice difference in types
map1 :: (t -> a, [t]) -> [a]
map3 :: (t -> a) -> [t] -> [a]
The function aroow associates to the right so
map3:: (t -> a) -> ([t] -> [a])
Notice curried functions return functions!
-}
-- Currying is most often useful when passing partially
-- applied functions to other higher-order functions:
pow n b = if n==0 then 1 else b * (pow (n-1) b)
w6 = map (pow 3) [1,2,3]
-- evaluates to [1,8,27]
-- we can store functions in data structures
-- we can create a list of functions
w7 = map pow [0,1,2,3,4]
[f0,f1,f2,f3,f4] = w7
-- try applying f0,f1 etc. to 2, or 5
-- Anonymous functions
w8 = map (\ x -> x+5) [2,5,7]
{-
Name derives from the Alonzo Church’s “lambda calculus,”
or iginally invented to study computability theory, in
which anonymous functions would be written using the
Greek lambda character.
-}
-- Lets capture another pattern (other than map)
-- Add a list of integers
sum2 [] = 0
sum2 (x:xs) = x + sum2 xs
-- multiply a list of integers
prod2 [] = 1
prod2 (x:xs) = x * prod2 xs
w9 = sum2 [3,4,6]
w10 = prod2 [2,6,3]
-- Here are a few more
copy [] =[]
copy (x:xs) = x : copy xs
len [] = 0
len (x:xs) = 1 + len xs
-- This function is called foldr in Haskell
-- I called it fold, so that it doesn't conflict.
fold f n [] = n
fold f n (x:xs) = f x (fold f n xs)
sum3 xs = fold (\ x y -> x+y) 0 xs
prod3 xs = fold (\ x y -> x*y) 1 xs
copy3 xs = fold (\ x y -> x : y) [] xs
len3 xs = fold (\ x y -> 1 + y) 0 xs
elem3 x xs = fold (\ item ans -> item==x || ans) False xs
{-
Function foldr computes a value working from the tail of
the list to the head (from right to left). Argument n is
the value to return for the empty list. Argument f is the
(Curried) function to apply to each element and the
previously computed result.
foldr (+) 8 (x1 : x2 : x3 : x4 : [])
x1 + x2 + x3 + x4 + 8
f x1 (f x2 (f x3 (f x4 8)))
-}
-- Accumulating functions
-- Recall
-- len [] = 1
-- len (x:xs) = 1 + len xs
-- Note that after the recursive call, we still need to add 1.
len4 [] n = n
len4 (x:xs) n = len4 xs (1+n)
len5 xs = len4 xs 0 -- note we need to intialize the acc
prod4 [] n = n
prod4 (x:xs) n = prod4 xs (x*n)
prod5 xs = prod4 xs 1 -- here intitialization is 1
-- We might do this because these functions are
-- tail recursive and can be efficiently compiled!
-- Another way to write tail recursive functions
-- is to use continuation style. Every function gets
-- an extra argument
len6 :: [a] -> (Int -> b) -> b
len6 [] k = k 0
len6 (x:xs) k = len6 xs (\ n -> k (n+1))
w12 = len6 [2,5,7] id
prod6 [] k = k 1
prod6 (x:xs) k = prod6 xs (\ n -> k (x*n))
{-
Notice that every call is now a tail-call . Note too that
len6 only returns after the outer k is invoked; in essence,
it needn’t return at all. If it were the whole program,
it wouldn’t need to return at all. This is because k is
serving the same role as a return address: saying “what
to do n ext.”
This means we can evaluate len6 without a stack !
Functions like k are called continuations and programs
written using them are said to be in continuation-passing
style (CPS)
We may choose to write (parts or all of) programs
explicitly in CPS because it makes it easy to express a
particular algorithm or because it clarifies the control
structure of the program.
Note that CPS programs are just a subset of ordinary
function al programs that happens to make heavy use of
the (existing) enormous power of first-class functions.
Remarkably, we can also systematica lly convert any
functional program into an equivalent CPS program.
(Details omitted.)
-}