Haskell - Higher order functions

Haskell functions can take functions as parameters and return functions as return values. A function that does either of those is called a higher order function. Higher order functions aren't just a part of the Haskell experience, they pretty much are the Haskell experience. It turns out that if you want to define computations by defining what stuff is instead of defining steps that change some state and maybe looping them, higher order functions are indispensable. They're a really powerful way of solving problems and thinking about programs.

All the functions that accepted several parameters so far have been curried functions. , so we will examine the curried function. 

e.g.of the curried function is as such .

ghci> max 4 5  
5  
ghci> (max 4) 5  
5  

 Function application is  simply putting a space between two things. Let's takes the max function for example. 

max :: (Ord a) => a -> a -> a

  can be read as such 

max :: (Ord a) => a -> (a -> a)

 If we call a funcion with too few parameters, we get back a partially applied function. with this, we can create function on the fly.

we will examine the following.

  • curried function 
  • partially applied function 
  • the associativity of the function.
  • Maps and filters. 
  • Lambdas 
  • foldl, foldr
  • scanl, scanr
  • function application with $, $ being left-associative
  • function composition with dot (.)  (f . g)(x) = f(g(x))

Let's see the some examle on the topics above. 

 

-- curried_functions.hs
--  demonstrate the use of the high-order function and we first start with the curried functions


-- a note from the authro from http://learnyouahaskell.com/higher-order-functions
--   QUOTE:  It turns out that if you want to define computations by defining what stuff is instead of defining steps that change some state and maybe looping them, higher order functions are indispensable. 


-- 
-- :curried functions

-- first by an example.
-- max 4 5
-- (max 4) 5


-- type examination
--   max :: (Ord a) => a -> a -> a   --  
--   max :: (Ord a) => a -> (a -> a) -- a function that takes an a and returns a function that takes an a and returns an a.
-- more examples

multThree :: (Num a) => a -> a -> a -> a
multThree x y z = x * y * z


-- let multTwoWithNine = multThree 9
-- multTwoWithNine 2 3 

-- let multTwoWithEighteen = multThree 9
-- multTwoWithEighteen 10

compareWithHundred :: (Num a, Ord a) => a -> Ordering
compareWithHundred x = compare 100 x


-- and we can re-write this function above with the curried form 
compareWithHundred' :: (Num a, Ord a) => a -> Ordering
compareWithHundred' = compare 100



-- sections
--
-- infix function can also be applied by using sections
-- QUOTE : To section an infix function, simply surround it with parentheses and only supply a parameter on one side. That creates a function that takes one parameter and then applies it to the side that's missing an operand. .

divideByTen :: (Floating a ) => a -> a
divideByTen = (/10) -- the left operand is missing, so if you apply divideByTen 200, then argument '200' will be supplied to the missing left-operand

isUpperAlphanum :: Char -> Bool
isUpperAlphanum = (`elem` ['A'..'Z'])

subtract4 :: (Num a)  => a -> a
subtract4 =  (subtract 4) -- you cannot use (-4), because this means minus 4
-- substract4 10

-- 
-- : Some higher-orderism is in order
applyTwice :: (a -> a) -> a -> a -- two notes: first, '->' operator is right associative, and second, the parenthesis is mandatory, because that means the first argument is an function, and the second argument is of the same type and returns a value of the same type
applyTwice f x = f (f x)
-- applyTwice (+3) 10		   
-- applyTwice (++ " HAHA") "HEY"
-- applyTwice (multThree 2 2) 9
-- applyTwice (3:) [1]

-- impls of the zipWith function
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c] -- the first argument is an function, which accept two argument of type a and b respectively and returns a value of type 'c', 
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys

-- zipWith' (+) [4, 2, 5, 6] [ 2, 6, 2, 3]
-- zipWith' max [6,3,2,1] [7,3,1,5]
-- zipWith' (++) ["foo ", "bar ", "baz "] ["fighters", "hoppers", "aldrin"]  
-- zipWith' (*) (replicate 5 2) [1..] 
-- zipWith' (zipWith' (*)) [[1,2,3],[3,5,6],[2,3,4]] [[3,2,2],[3,4,5],[5,4,3]]  

-- impls of the flip' function, which flip the first two arguments
flip' :: (a -> b -> c) -> (b -> a -> c)
flip' f = g
  where g x y = f y x

--flip' zip [1, 2, 3, 4, 5] "hello"


--
-- :Maps and filters

-- map takes a function and a list and applies that function to every element in the list.   
map' :: (a -> b) -> [a] -> [b]
map' _ [] = []
map' f (x : xs) = f x : map' f xs


-- map (+3) [1, 5, 3, 1, 6]
-- map (++ "!") ["BIFF", "BANG", "POW"]
-- map (replicate 3) [3..6]
-- map (map (^2)) [[1,2],[3,4,5,6],[7,8]]  -- double applied map function to List[List]
-- map fst [(1,2),(3,5),(6,3),(2,6),(2,5)]

-- 
-- :filter
filter' :: (a -> Bool) -> [a] -> [a]
filter' _ [] = []
filter' p (x:xs) 
    | p x = x : filter' p xs
    | otherwise = filter' p xs
-- filter (>3) [1,5,3,2,1,6,4,3,2,1]      
-- filter (==3) [1,2,3,4,5]
-- filter (==3) [1,2,3,4,5]
-- let notNull x = not (null x) in filter notNull [[1,2,3],[],[3,4,5],[2,2],[],[],[]] 
-- filter (`elem` ['a' .. 'z'] "u LaUgH aT mE BeCaUsE I aM diFfeRent"  
-- filter (`elem` ['A'..'Z']) "i lauGh At You BecAuse u r aLL the Same"  	   
-- now let's redefine the quick sort function
quicksort :: (Ord a ) => [a] -> [a]
quicksort [] = []
quicksort (x : xs) = 
  let smallerSorted = quicksort (filter (<=x) xs)
      biggerSorted = quicksort (filter (>x) xs)
  in smallerSorted ++ [x] ++ biggerSorted
-- some performance NOTE: Thanks to Haskell's laziness, even if you map something over a list several times and filter it several times, it will only pass over the list once.  


-- e.g by case :  find the largest number under 10,000 that is divisible by 3829
largestDivisible :: (Integral a) => a
largestDivisible = head (filter p [10000, 9999..])
  where p x = x `mod` 3829 == 0

-- sum of of all odd squares that are smaller than 10,000.
-- sum (takeWhile (<10000) (filter odd (map (^2) [1..])))
-- sum (takeWhile (<10000) [n^2 | n <- [1..], odd (n^2)]) -- it is a matter of taste

-- chain
chain :: (Integral a) => a -> [a]
chain 1 = [1]
chain n 
  | even n = n : chain(n `div` 2)
  | odd n = n : chain(n * 3 + 1)

numLongChains :: Int
numLongChains = length (filter isLong (map chain[1..100]))
  where isLong xs = length xs > 15

-- map to get a list of functions
-- let listOfFuns = map (*) [0..]
-- (listOfFuns !! 4) 5 -- !! is index operator


--
-- : Lambdas
-- lambdas are basically anonymous functions that are used because we need some functions only once.
-- QUOTE: we write a \ (because it kind of looks like the greek letter lambda if you squint hard enough) and then we write the parameters, separated by spaces. After that comes a -> and then the function body. We usually surround them by parentheses, because otherwise they extend all the way to the right. 

numLongChains' :: Int
numLongChains' = length (filter (\xs -> length xs > 15) (map chain [1..100]))

-- guideline of partially applied function vs. lambda expressions
-- use this
-- map (+3) [1, 6, 3, 2]
-- do NOT use this
-- map (\x -> x + 3) [1, 6, 3, 2]
-- e.g. of lambda expression 
-- zipWith (\a b -> (a * 30 + 3) / b) [5,4,3,2,1] [1,2,3,4,5]  

-- careful with pattern matching in lambda, because 
-- ONCE a pattern matching fails in a lambda, a runtime error occurs. 
-- map (\(a, b) -> a + b ) [(1,2),(3,5),(6,3),(2,6),(2,5)] 

-- normally by parenthesis, unless we mean for them to extend all the way to the right.
addThree :: (Num a) => a -> a -> a -> a
addThree x y z = x + y + z

addThree' :: (Num a) => a -> a -> a -> a
addThree' =  \x -> \y -> \z -> x + y + z -- not a perfect style of writing the fun

-- but depends on the times
flip'' :: (a -> b -> c) -> b -> a -> c
flip'' f = \x y -> f y x


-- 
-- : only folds and horses (topics on the foldLeft, foldRight?)
-- fold1, leftFold

sum' :: (Num a) => [a] -> a
sum' xs = foldl (\acc x -> acc + x) 0 xs

-- sum' [3, 5, 2, 1]

-- re-impl of the `elem` operator again, but again with the fold1 funtion
elem' :: (Eq a) => a -> [a] -> Bool
elem' y ys = foldl (\acc x -> if x == y then True else acc) False ys

--
-- :: foldr
-- re-impl of the map' with foldr
map'r :: (a -> b) -> [a] -> [b]
map'r f xs = foldr (\x acc -> f x : acc) [] xs
-- a foldl impls is as follow, however, much more expensive. 
-- map'l :: (Num a) => [a] -> a
-- map'l f xs = foldl (\accx -> acc ++ [f x]) [] xs -- ++ operator is too expensive.

-- a review point of Folds is as follow 
-- QUOTE : Folds can be used to implement any function where you traverse a list once, element by element, and then return something based on that. Whenever you want to traverse a list to return something, chances are you want a fold. 

-- foldl1, and foldr1

maximum'' :: (Ord a) => [a] -> a  
maximum'' = foldr1 (\x acc -> if x > acc then x else acc)  
  
reverse'' :: [a] -> [a]  
reverse'' = foldl (\acc x -> x : acc) []  
  
product'' :: (Num a) => [a] -> a  
product'' = foldr1 (*)  
  
filter'' :: (a -> Bool) -> [a] -> [a]  
filter'' p = foldr (\x acc -> if p x then x : acc else acc) []  
  
head'' :: [a] -> a  
head'' = foldr1 (\x _ -> x)  
  
last'' :: [a] -> a  
last'' = foldl1 (\_ x -> x)  

-- scanl, scanr
-- like foldl, and foldr, but only report all the intermediate accumulator state in the form of a list. 
-- scanl (+) 0 [3, 5, 2, 1]
-- scanr (+) 0 [3, 5, 2, 1]
-- scanl1 (\acc x -> if x acc then x else acc) [3,4,5,3,7,9,2,1]
-- scanl (flip (:)) [] [3,2,1]
-- e.g. How many elements does it take for the sum of the roots of all natural numbers to exceed 1000? 

sqrtSums :: Int
sqrtSums = length (takeWhile (<1000) (scanl1 (+) (map sqrt [1..]))) + 1
-- verify that your code is right

-- sum (map sqrt [1..131])
-- sum (map sqrt [1..130])

-- 
-- : Function application with $
--   QUOTE : 
--     Alright, next up, we'll take a look at the $ function, also called function application. First of all, let's check out how it's defined:

-- the definition of the ($) operator
-- ($) :: (a -> b) -> a -> b
-- f $ x = f x

-- some facts about the '$' operator
--  1. normal function application (putting a space between two things) has a really high precedence, the $ function has the lowest precedence. 
--  2. Function application with a space is left-associative (so f a b c is the same as ((f a) b) c)), function application with $ is right-associative.

-- some e.g.
-- sum (map sqrt [1..130])
-- sum $ map sqrt [1..130]

-- sqrt $ 3 + 4 + 9
-- sqrt (3 + 4 + 9)

-- sum (filter (>10) (map (*2) [2..10]))
-- sum $ filter (>10) $ map (*2) [2..10]    


-- 
-- : Function composition
-- QUOTE: n mathematics, function composition is defined like thisn mathematics, function composition is defined like this : (f . g) (x) = f(g(x))
--  which means that composing two functions produces a new function that 
-- when called with parameter, say x is equivalent of calling g with the parameter x and then calling the f with that result
--
--  the haskell function composition 
--
-- the definition of the 'function composition'
-- (.) :: (b -> c) -> (a -> b) -> a -> c
-- f . g = \x -> f (g x)

-- e.g. of the function composition 
-- k = \x -> negate . (* 3) x
-- k = negate . (* 3) 

-- e.g. of use lambda    
-- map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24]
-- e.g of use function composition 
-- map (negate . abs) [5,-3,-6,7,-3,2,-19,24] 

-- map (\xs -> negate (sum (tail xs))) [[1..5],[3..6],[1..7]] 
-- map (negate . sum . tail) [[1..5],[3..6],[1..7]]  -- with composition style

-- replicate 100 (product (map (*3) (zipWith max [1,2,3,4,5] [4,5,6,7,8])))
-- replicate 100 . product . map (*3) . zipWith max [1,2,3,4,5] $ [4,5,6,7,8]    
-- because only with 'infix' operator, you can apply 'section' on those
-- infix operator (such as +, -, etc...) but you cannot use 
-- section operator 
-- 
-- map (negate $ sum $ tail)[[1..5],[3..6],[1..7]]

sum'pointer_style :: (Num a) => [a] -> a
sum'pointer_style xs = foldl (+) 0 xs

-- and the point-free styles is written as such 
-- 
sum'pointer_free_style :: (Num a) => [a] -> a
sum'pointer_free_style  = foldl (+) 0

-- the following is as follow. 
--
-- fn x= ceiling (negate (tan (cos (max 50 x))))
-- fn = ceiling . negate . tan . cos . max 50
-- fn = ceiling $ negate $ tan $ cos $ max 50  60
-- fn = ceiling $ negate $ tan $ cos $ max 50 $ 60

-- style of choice
-- which of the following style is more readable
oddSquareSum :: Integer
oddSquareSum = sum (takeWhile (<10000) (filter odd (map (^2) [1..])))

oddSquareSum' :: Integer
oddSquareSum' = sum . takeWhile (<10000) . filter odd . map (^2) $ [1..]

oddSquareSum'' :: Integer
oddSquareSum'' = 
   let oddSquares = filter odd $ map (^ 2) [1..]
       belowLimit = takeWhile (<10000) oddSquares
   in sum belowLimit

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值