Functional Programming Charlie Li 6/4/2019 Content
• Functional Programming • Haskell – Features • Haskell – Basic Types • Haskell – Classes • Haskell – Custom Types • Haskell – Infix Function • Haskell – Precedence and Associativity • Haskell – Partial Application Content
• Haskell – Strict Type • Haskell – Syntax • Haskell – Pattern Matching • Haskell – Lists • Haskell – Folds • Haskell – Tuples • Haskell – Challenges • Haskell – More Functional Programing
• Why functional programing?
• Pros: • Less bug • Shorter and cleaner code • Focus on WHAT to calculate not HOW to calculate
• Cons: • Looonger programming time and runtime • Compile errorsss Functional Programing
• Pure function • A pure function is a function which gives the same result with the same input • No side effects • Outputting to screen is a side effect! Functional Programing
• Which of the following functions are pure functions? void f1() { cout << "Hello World !" << endl; } int f2(int x) { return ++x; } int f3(int& x) { return ++x; } int f4(int x, int y) { return x + y; } Functional Programing
• f1 is not a pure function!
• It print the same string every time, isn’t it a constant function?
• No, printing is a side effect, it changes your screen! • More precisely, it changes some part of the memory that does not belong to it Functional Programing
• f2 is a pure function!
• It has not side effects.
• Although it changes the variable x, that x belongs to the function, not the outside world, so it is pure Functional Programing
• f3 is not a pure function!
• It has side effect.
• Noted that x is passed by reference. • It changes the variable x which does not belong to itself Functional Programing
• f4 is a pure function!
• It has no side effects.
• It returns the same output when the input is the same. Functional Programing
• In a pure functional programing language, every function is a pure function • All variables are mathematically variables • It has Referential transparency, i.e. replacing the variable by its value does not change the behavior of the program • Global variables are essentially global constant • No input and output in the function • IO is troublesome and too difficult to be explained today Haskell - Features
• Haskell is a pure functional programming language • Every function is a pure function
• Haskell is statically typed • The type of variables are determined at compile time
• Haskell supports type inference • The compile can detect the type of variables and functions even if it is not provided
• Haskell supports lazy evaluation • The value of an expression is evaluated only if it is needed Haskell – Basic Types
• Names of data types always starts with capital letter
• Some basic types are: • Int • A 32-bit or 64-bit integer, depends on computer • Integer • Aka. Big integer • Double • Char • Bool • String Haskell – Basic Types
• There are also two more kind of useful data types in Haskell
• Lists • Every element are of the same type in a list (unlike Python) • There is no restriction on the length of lists • Yes, there are lists with infinite length
• Tuples • Elements in a tuple may have different types • The largest tuple can have up to 62 entries Haskell – Basic Types
• Lists • List are represented using brackets [] • [1, 2, 3] is a list of integer with length 3 • Its type maybe [Int] or [Double] or …
• Tuples • Tuples are represented using parenthesis () • (1, ‘a’, [True, False]) is a tuple with three element • Its type maybe (Int, Char, [Bool]) or (Double, Char, [Bool]) or … Haskell Platform
• Haskell Platform includes GHC (the Haskell compiler) and other useful tools
• The latest version is 8.6.3, the installer for Windows can be downloaded here • You can find the download links for other OS at the bottom Haskell – Area
• Let’s open ghci (winghci has better UI but cannot be used in this lab due to some permission issues) • Let’s first define a function to calculate the area of a rectangle
• Type the following into ghci (let areaRect l w = l * w):
• This is the definition of the function areaRect
• What do you think will be the output of areaRect 2 3? Haskell – Area
• Bingo
• How about areaRect 2.5 3.5? Haskell – Area
• Bingo
• Next question, how about areaRect 2.5 3? Haskell – Area
• Bingo
• Next question, what do you think will be the type of areaRect? • Sometimes it takes two integers as input and return integer • Sometimes it takes two fractional numbers as input and return a fractional number Haskell – Area
• In ghci, we can type :t name to check the type (aka. signature) of a function
• The “::” tells that the following is the function type not a definition
• The “Num a =>” part says that a is a type that belongs to the “Num” class • “Num” class contains all kinds of numbers, e.g. Int, Integer, Float, Double, … Haskell – Area
• In ghci, we can type :t name to check the type (aka. signature) of a function
• The “a -> a -> a” part is the type of the function • The last one is the type of the returned value • Others are the type of parameter • So area takes two parameter with the same type as input and return the same type • Also, this a must be some kind of number Haskell – Area
• In ghci, we can type :t name to check the type (aka. signature) of a function
• So this tells us that areaRect is a function that takes two numbers as input and return a number Haskell – Classes
• Be careful, there is a bit difference between class and type
• Haskell is a statically typed language • The existence of class is for safe function overload
• There are similarity between class in Haskell and class in C++ • There are some inheritance relation • E.g. Integral a => Num a • But the class in Haskell is not the same as the class in C++ Haskell – Classes
• There are many predefined classes in Haskell, the name are usually descriptive enough Haskell – Classes
• Eq: All types that == and /= are defined • Ord: All types that <, >, <=, >= are defined • Num: All types of numbers • Real: All types of real numbers • Fractional: All types of numbers that are not necessarily integer • Integral: All types of integers • Foldable: All types that “foldr” is defined • Read: All types that “read” is defined • Show: All types that “show” is defined • … Haskell – Classes
• So now do you know the type of [1, 2, 3] and (1, ‘a’, [True, False]) ?
• How to check if you get the correct answer? • Just try to type :t [1, 2, 3] and :t (1, ‘a’, [True, False]) Haskell – Classes
• Did you get it right? Haskell – Custom Type
• In Haskell you can define your own type • Syntax like this: data Contact = Phone Integer | Email String data Maybe a = Just a | Nothing data Either a b = Left a | Right b data Ordering = LT | EQ | GT
• Phone, Email, … are said to be the constructors of the corresponding type • Maybe, Either and Ordering are predefined in Haskell Haskell – Custom Type
• You can use Notepad++ to write the code and save as “.hs”
• Suppose your code is saved as “C:\Haskell\code\sample.hs” • You need to type the following to load the code into ghci :load C:\Haskell\code\sample.hs
• You will see the “Prelude” become “Main” if it is loaded successfully Haskell – Custom Type
• Phone and Email are constructors of Contact • They are also functions with the following type Haskell – Custom Type data Contact = Phone Integer | Email String data Maybe a = Just a | Nothing data Either a b = Left a | Right b data Ordering = LT | EQ | GT
• Maybe, Either are kinds and NOT classes nor types • Contact, Maybe Int are types • Phone 98765432, Just 0, Nothing are values Haskell – Infix Function
• A function with symbol only is assumed to be infix operator • Need to add a pair of brackets to make it like normal functions
-- x + y == (+) x y
• A function can become infix if it is wrapped by `` add x y = x + y -- add x y == x `add` y Haskell – Precedence and Associativity
• Infix functions has lower precedence then normal functions • Normal functions are left associated • i.e. f a b == (f a) b • Associativity of infix functions depend on the function • Usually left associated • Some right associated functions: exponent, (.), ($) ($) :: (a -> b) -> a -> b -- trick: f $ g $ h x == f (g (h x)) /= ((f g) h) x == f g h x Haskell – Anonymous Function
• In Haskell, it is able to define anonymous function using lambda expression.
• Syntax: \var1 var2 … varn -> expression • The expression goes as far as possible • Remember to add parentheses • Example: \x -> x + 1 \x y -> x + y Haskell – Anonymous Function
• Remember when will you use anonymous function in C++? sort(v.begin(), v.end(), [] (int x, int y) { return x > y; }); • You can also write something similar in Haskell • There is a sortBy function in Haskell (in the package Data.List) • To import Data.List into ghci, simply type import Data.List sort :: Ord a => [a] -> [a] sortBy :: (a -> a -> Ordering) -> [a] -> [a] Haskell – Anonymous Function sort [1, 4, 2, 3] -- [1, 2, 3, 4] sortBy (\x y -> negate x `compare` negate y) [1, 4, 2, 3] -- [4, 3, 2, 1]
-- negate :: Num a => a -> a -- compare :: Ord a => a -> a -> Ordering -- *this is not the best way to define it, why? Haskell – Unary Functions
• Actually all functions can be viewed as unary functions • How about (+) which takes two parameter?
(+) :: Num a => a -> a -> a -- can be viewed as (+) :: Num a => a -> (a -> a) -- (+) x == \y -> x + y
• (->) is right associative • (+) takes one input (number) and returns one output (function) Haskell – Partial Application
• Consider add x y = x + y addTwo = add 2
• What is the type of addTwo?
• This is called partial application. Haskell – Partial Application
• Here comes some function that are often applied partially const :: a -> b -> a -- usually be viewed as const :: a -> (b -> a) flip :: (a -> b -> c) -> b -> a -> c -- usually be viewed as flip :: (a -> b -> c) -> (b -> a -> c) (.) :: (b -> c) -> (a -> b) -> a -> c -- usually be viewed as (.) :: (b -> c) -> (a -> b) -> (a -> c) -- aka. function composition map :: (a -> b) -> [a] -> [b] -- sometimes be viewed as map :: (a -> b) -> ([a] -> [b]) Haskell – Partial Application
• When defining a function, we can actually define it partially • But all definition of the same function must have the same number of parameters (when you are doing pattern matching) • The following three definition is exactly the same add1 x y = x + y add2 x = (+) x add3 = (+) Haskell – Partial Application
• Suppose now you want to define a function sumSquare to calculate the sum of all the elements’ square in a list
• The following one works perfectly sumSquare = sum . map (^ 2) -- sum :: (Flodable t, Num a) => t a -> a -- (^), map, (.) are all applied partially Haskell – Partial Application
• Challenge: • Define sortDesc which sort the element in descending order in one line. • It should have the signature sortDesc :: Ord a => [a] -> [a] Haskell – Area 2
• This time, let’s define a function to calculate the area of a circle
• For example, if I define it like this (let areaCirc r = pi * r^2):
• What will be the type of areaCirc? Haskell – Area 2
• Why? • Because (*) must take two parameter with the same type • And • Also the input and output of (^) need to have the same type • Therefore the compiler deduced that areaCirc must also take some Floating a Haskell – Area 2
• What if we pass an value with type Int into areaCirc ?
• Oh, error • How to solve this? Haskell – Area 2
• In this case, an explicit type conversion is required • fromIntegral is the function which can change an integral value to any kind of number formIntegral :: (Integral a, Num b) => a -> b
• This time it works! Haskell – Strict Type
• Unlike C++, Haskell is very strict on the variable type • E.g. you cannot multiply an Int with a Double
• Although the code is longer, this can assure that the programmer will not accidentally mixed up two types and helps preventing bugs Haskell – Strict Type
• As a result, some mathematic operators may look different from the one in C++ • There are two divisions, one for Fractional, one for Integral div :: Integral a => a -> a -> a (/) :: Fractional a => a -> a -> a • There are three different kinds of exponent (aka. pow) (^) :: (Integral b, Num a) => a -> b -> a (^^) :: (Fractional a, Integral b) => a -> b -> a (**) :: Floating a => a -> a -> a Haskell – Local Definition
• Now, let’s define a function to calculate the area of a triangle given the length of the three sides
• This can be calculated using the Heron’s fomula Haskell – Local Definition
• Let’s write this two lines area a b c = sqrt (s * (s - a) * (s - b) * (s - c)) s = (a + b + c) / 2 • And then load the file into ghci Haskell – Local Definition
• Oops
• What are abc in the definition of s ? Haskell – Local Definition
• We can use a where clause to make a local definition area a b c = sqrt (s * (s - a) * (s - b) * (s - c)) where s = (a + b + c) / 2
• Both the word where and local definition are indented 4 spaces to separate from the top level definitions Haskell – Local Definition
• Besides where clause, one can also use a let-in clause to do the same job area a b c = let s = (a + b + c) / 2 in sqrt (s * (s - a) * (s - b) * (s - c))
• Noted that the lines are indented by 4 spaces to separate from top- level definitions Haskell – if-then-else
• Suppose you want to define the factorial function, we may want to use the if-then-else statement • The syntax is like this factorial :: Integer -> Integer factorial x = if x == 0 then 1 else x * factorial (x - 1) • Unlike in C++, the else part is necessary • Otherwise the function may return nothing • Noted that the lines are indented by 4 spaces to separate from top-level definitions Haskell – case-of
• Suppose you want to define the fib function (fibonacci), we may want to use the case-of statement • The syntax is like this fib :: Integer -> Integer fib x = case x of 1 -> 1 2 -> 1 otherwise -> fib (x-1) + fib(x-2) • otherwise is similar to default in C++ • Noted that the lines are indented by 4 spaces to separate from top-level definitions Haskell – Guard
• Actually there is a more elegant way to define fib using guard • The syntax is like this: fib :: Integer -> Integer fib x | x <= 2 = 1 | otherwise = fib (x-1) + fib (x-2) • Noted that the two | are in the same column • Here, otherwise is the same as true • Does it looks like this? • Ps. There is an even elegant way to define fib using infinite list Haskell – Pattern Matching
• And there is also a more elegant way to define factorial using pattern matching • The syntax is like this: factorial :: Integer -> Integer factorial 0 = 1 factorial x = x * factorial (x-1) • When the function factorial is called, it will start to match the pattern from top of the file to bottom • It will use the first matched pattern as the definition • Can only match on concrete value or constructor • Runtime error occurs if nothing is matched Haskell – Pattern Matching
• We can use pattern matching to match the constructors data Contact = Phone Integer | Email String • You can define the following function: information (Phone n) = "Phone number is " ++ show n information (Email m) = "Email address is " ++ m • Producing the following result Haskell – Construction of List
• Four ways to generate a list using “..”
• [a..b]
--[1..5] == [1, 2, 3, 4, 5] --[1.5..5.5] == [1.5, 2.5, 3.5, 4.5, 5.5] --[1.5..5] == [1.5, 2.5, 3.5, 4.5] --['a'..'e'] == "abcde" Haskell – Construction of List
• Four ways to generate a list using “..”
• [a,b..c]
--[1, 3..10] == [1, 3, 5, 7, 9] --[1.5, 2..5] == [1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5] --[1.5, 2..3.75] == [1.5, 2, 2.5, 3, 3.5] --['a', 'd'..'z'] == "adgjmpsvy" Haskell – Construction of List
• Four ways to generate a list using “..” • [a..] (infinite list, don’t input this into ghci since it never ends)
--[1..] == [1, 2, 3, 4, 5, …] --[1.5..] == [1.5, 2.5, 3.5, 4.5, …]
• [a, b..] (infinite list, don’t input this into ghci since it never ends)
--[1, 3..] == [1, 3, 5, 7, 9, …] --[1.5, 2..] == [1.5, 2, 2.5, 3, …] Haskell – List Comprehension
• We can obtain a list from other lists • Syntax: [expression | predicate, predicate, …] • predicate can be a Bool or a statement in the form var <- list
• Example: --[x | x <- [1..20], x < 5] == [1, 2, 3, 4] --[2 * x | x <- [1..20], x < 5] == [2, 4, 6, 8] --[x | x <- [1..20], even x, x < 10] == [2, 4, 6, 8] --[(x, y) | x <- [0,1], y <- [0,1]] == [(0, 0), (0, 1), (1, 0), (1, 1)] Haskell – List
• We can build lists by concatenating two lists or insert an element into a list
[] :: [a] -- the empty list (:) :: a -> [a] -> [a] -- inserting new element at the beginning of a list (++) :: [a] -> [a] -> [a] -- concatenating two lists Haskell – List
--1:[] == [1] --1:2:3:[] == 1:2:[3] == 1:[2, 3] == [1, 2, 3] --[]:[] = [[]] --[] ++ [] == [] --[1] ++ [2] == [1, 2] --[1, 4] ++ [3, 2] == [1, 4, 3, 2] Haskell – Pattern Matching of Lists
• [] and (:) can be used in pattern matching but (++) cannot • the computer do not know where to cut the list
• Let’s implement a function myLength which returns the length of the input list
• First, the signature of myLength should be myLength :: [a] -> Int Haskell – Pattern Matching of Lists
• Then we should separate into two cases, the list is empty and non empty • When the list is empty mylength [] = 0 • When the list is non-empty myLength (_:as) = mylength as + 1 • Here “_” is a filler, it means that there is actually something but we don’t care what it is • It is strange but Notepad++ will highlight the word “as” Haskell – Pattern Matching of Lists
• So finally we have myLength :: [a] -> Int myLength [] = 0 myLength (_:as) = myLength as + 1 • Which is the same as myLength :: [a] -> Int myLength (_:as) = myLength as + 1 myLength _ = 0 • Noted that the order is revered, since we know that [] cannot match any (_:as) Haskell – Pattern Matching of Lists
• Challenge: • Can you define (++) :: [a] -> [a] -> [a] ? Haskell – Folds
• Have you realized that there are some kind of statement that appears in C++ and Python that is missing in Haskell? Haskell – Folds
• Loops! • E.g. for (int i = 0; i < n; i++) while (l < r) Haskell – Folds
• Without for loop, how can we do binary search? • Recursion! (more precisely, recursive definition) binarySearch l r f | l+1 == r = l | f m = binarySearch l m f | otherwise = binarySearch m r f where m = (l + r) `div` 2
• Challenge: What is the type of binarySearch ? Haskell – Folds
• Without for loop, how can we find the sum of a list? • Recursion! (more precisely, recursive definition) sum [] = 0 sum (a:as) = a + sum as Haskell – Folds
• Without for loop, how can we find the product of a list? • Recursion! (more precisely, recursive definition) product [] = 1 product (a:as) = a * product as Haskell – Folds
• Don’t you think that finding the sum and the product are similar?
• Actually, there is a more elegant way to define sum and product using folds Haskell – Folds foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b -- foldr f z [x1, x2, ..., xn] == x1 `f` (x2 `f` ... (xn `f` z)...) foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b -- foldl f z [x1, x2, ..., xn] == (...((z `f` x1) `f` x2) `f`...) `f` xn foldl1 :: Foldable t => (a -> a -> a) -> t a -> a -- foldl1 f a == foldl f (head a) $ tail a foldr1 :: Foldable t => (a -> a -> a) -> t a -> a -- foldl1 and foldr1 will throw exception if the input list is empty Haskell – Folds
• Fun fact: if you want to obtain a single value from a list, what you need should be one of the folds
sum = foldr (+) 0 product = foldr (*) 1 maximum = foldr1 max Haskell – Folds
• Challenge: • Can you define foldr for lists? • Can you define foldl for lists in terms of foldr? • Can you define the following functions in one line? reverse :: [a] -> [a] (++) :: [a] -> [a] -> [a] concat :: [[a]] -> [a] Haskell – Tuples
(,) :: a -> b -> (a, b) -- constructor of pair (,,) :: a -> b -> c -> (a, b, c) -- constructor of 3-tuple --… --Up to 62-tuple Haskell – Tuples Functions fst :: (a, b) -> a -- same as .first of pair in C++ snd :: (a, b) -> b -- same as .second of pair in C++ curry :: ((a, b) -> c) -> a -> b -> c -- can be viewed as ((a, b) -> c) -> (a -> b -> c) -- e.g. curry fst == const uncurry :: (a -> b -> c) -> (a, b) -> c -- can be viewed as (a -> b -> c) -> ((a, b) -> c) -- e.g. uncurry (+) (2, 3) == 5 Haskell – Tuples Functions zip :: [a] -> [b] -> [(a, b)] -- zip [10] [1, 2] == [(10, 1)] unzip :: [(a, b)] -> ([a], [b]) zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] -- zipWith (^) [10, 20, 30] [1, 2] == [10, 400] Haskell – Pattern Matching of Tuples
• A simple definition of fst and snd can be achieved using pattern matching: fst (x, y) = x snd (x, y) = y Haskell – Tuples
• Challenge: • Can you write a definition of zip in one line? • Can you define fst and snd using uncurry? Haskell – Challenges
• Define sortDesc which sort the element in descending order in one line. • It should have the signature sortDesc :: Ord a => [a] -> [a] • Can you define (++) :: [a] -> [a] -> [a]? • What is the type of binarySearch at slide 72? • Can you define foldr for lists? • Can you define foldl for lists in terms of foldr? • Can you define the following functions in one line? reverse :: [a] -> [a] (++) :: [a] -> [a] -> [a] concat :: [[a]] -> [a] Haskell – Challenges
• Can you write a definition of zip in one line? • Can you define fst and snd using uncurry? • Can you define subsequence :: [a] -> [[a]] which returns all the subsequence of a list? Haskell – Challenges
• Can you define an infinite list of square numbers in one line? • Can you define an infinite list of Fibonacci numbers in one line? • Can you define an infinite list of prime numbers? • Using Sieve of Eratosthenes and recursive definition • You can find one somewhere • Can you define an infinite list of Catalan numbers? Haskell – More
• You can read more on Wikibook • Search package and functions on Hoogle • Official website of Haskell Language Solution to Challenges • Some of the challenges are really challenging, it is normal if you can only solve some of them
• You may get more idea about functional programming by looking at the solution • Define sortDesc which sort the element in descending order in one line. • It should have the signature sortDesc :: Ord a => [a] -> [a] • Define sortDesc which sort the element in descending order in one line. • It should have the signature sortDesc :: Ord a => [a] -> [a]
sortDesc = sortBy $ flip compare Or sortDesc = reverse . sort • Can you define (++) :: [a] -> [a] -> [a]? • Can you define (++) :: [a] -> [a] -> [a]?
[] ++ b = b (a:as) ++ b = a : (as ++ b) • What is the type of binarySearch? binarySearch l r f | l+1 == r = l | f m = binarySearch l m f | otherwise = binarySearch m r f where m = (l + r) `div` 2 • What is the type of binarySearch? binarySearch l r f | l+1 == r = l | f m = binarySearch l m f | otherwise = binarySearch m r f where m = (l + r) `div` 2 binarySearch :: Integral a => a -> a -> (a -> Bool) -> a • Can you define foldr for lists? • Can you define foldr for lists? foldr f z [] = z foldr f z (a:as) = a `f` foldr f z as • Can you define foldl for lists in terms of foldr? • Can you define foldl for lists in terms of foldr? foldl f z = foldr (flip f) z . reverse • Can you define reverse :: [a] -> [a] in one line? • Can you define reverse :: [a] -> [a] in one line? reverse = foldl (flip (:)) [] • Can you define (++) :: [a] -> [a] -> [a] in one line? • Can you define (++) :: [a] -> [a] -> [a] in one line?
(++) = flip $ foldr (:) • Can you define concat :: [[a]] -> [a] in one line? • Can you define concat :: [[a]] -> [a] in one line? concat = foldr (++) [] -- more preferable Or concat = foldl (++) [] • Can you write a definition of zip in one line? • Can you write a definition of zip in one line? zip = zipWith (,) • Can you define fst and snd using uncurry? • Can you define fst and snd using uncurry? fst = uncurry const snd = uncurry $ flip const • Can you define subsequence :: [a] -> [[a]] which returns all the subsequence of a list? • Can you define subsequence :: [a] -> [[a]] which returns all the subsequence of a list? subsequence [] = [[]] subsequence (a:as) = map (a:) subseq ++ subseq where subseq = subsequence as • Can you define an infinite list of square numbers in one line? • Can you define an infinite list of square numbers in one line? squares = map (^2) [1..] • Can you define an infinite list of Fibonacci numbers in one line? • Can you define an infinite list of Fibonacci numbers in one line? fib = 1 : zipWith (+) (0:fib) fib • Can you define an infinite list of prime numbers? • Using Sieve of Eratosthenes and recursive definition • You can find one somewhere • Can you define an infinite list of prime numbers? • Using Sieve of Eratosthenes and recursive definition • You can find one somewhere primes = filterPrime [2..] where filterPrime (p:xs) = p : filterPrime [x | x <- xs, x `mod` p /= 0] • Can you define an infinite list of Catalan numbers? • Can you define an infinite list of Catalan numbers? catalan = map (fst.catalan') [1..] where catalan' 1 = (1, [1]) catalan' x = (res, res:ls) where res = sum $ zipWith (*) ls $ reverse ls (_, ls) = catalan' $ x-1