Download presentation
Presentation is loading. Please wait.
1
MPCS 51400: Functional Programming
Lecture 3: Higher Order Functions, Polymorphism, TypeClasses, List comprehension MPCS 51400: Functional Programming
2
Anonymous functions (Review)
Anonymous function is a function without a name. using the lambda syntax represented by a backslash: Prelude> \x -> x :: Integer -> Integer Prelude> (\ x -> x + 2 ) 3 5 Here’s two ways to write the triple function: {- Named function -} triple :: Integer -> Integer triple x = x * 3 {- Anonymous function -} (\x -> x * 3) :: Integer -> Integer Why Anonymous functions? Mostly used as a function that is passed to a higher-order function (more on this next) and that’s the only place you are going to use that particular function.
3
Higher Order Functions
Higher-order functions (HOFs) are functions that accept functions as arguments: flip (defined in the Prelude) takes in a function and two arguments and applies the function to the arguments in reversed order Prelude> :t flip flip :: (a -> b -> c) -> b -> a -> c flip is a (HOF) because it takes in a function (a -> b -> ->c). Notice the ( … ) around the function. You need the parenthesis to specify you are passing in a function; otherwise it would look as though you are passing in 6 arguments. The implementation of flip can be defined as: myFlip :: (a -> b -> c) -> b -> a -> c myFlip f x y = f y x flipAnon :: (a -> b -> c) -> b -> a -> c flipAnon f = \ x y -> f y x
4
Higher Order Functions EXAMPLES
myFlip :: (a -> b -> c) -> b -> a -> c myFlip f x y = f y x flipResult = myFlip (-) == 1 flipResult2 = myFlip (\ x y -> x ^ 2 + y) ^2 + 7 == 71 myFunc x y = x ^ 2 + y flipResult3 = myFlip myFunc ^ == 91
5
Function Composition Function composition allows you to combine/chain multiple functions together such that the result of applying one function gets passed to the next function as an argument: (.) :: (b -> c) -> (a -> b) -> a -> c Similar to function composition in math: (f . g) x = f (g x)
6
Function Composition Prelude> negate . sum $ [1, 2, 3, 4, 5] -15
-- which is evaluated like this negate . sum $ [1, 2, 3, 4, 5] -- note: this code works as well negate (sum [1, 2, 3, 4, 5]) negate (15)
7
PointFree Style Refers to a style of composing functions without specifying their arguments: Prelude> let f = negate . sum Prelude> f [1, 2, 3, 4, 5] -15 Prelude> let f = length . filter (== 'a') Prelude> f "abracadabra" 5 This style makes code much cleaner and clearer because the reader focuses on the function rather than the data.
8
HIGHER ORDER FUNCTIONS
What if we wanted to write a sumSquares or sumCubes function on lists? One implementation could be: sumSquares [] = 0 sumSquares (x:xs) = x^2 + sumSquares xs sumCubes [] = 0 sumCubes (x:xs) = x^3 + sumCubes xs But if you notice these functions look very similar. Could we make this code more modular and reuse portions of code? Yes, Higher- Order Functions! © 2009–2016 Ravi Chugh, Stuart A. Kurtz
9
Higher-Order List Functions
As programmers you don’t want to write the same code over and over again. Thus, you want to find appropriate abstractions that that capture the relevant commonalities, and then express the specific versions as special cases. sumf f [] = 0 sumf f (c:cs) = f c + sumf f cs square x = x^2 cubes x = x^3 sumSquares xs = sumf square xs sumCubes xs = sumf cubes xs This second version is to be preferred because it achieves a clean factoring of the problem into a recursive summing part and a function computing part, whereas in the first version these aspects are intertwined. © 2009–2016 Ravi Chugh, Stuart A. Kurtz
10
Higher-Order List Functions
But we can do even better then this by using other HOFs such as map: map f [] = [] map f (x:xs) = f x : map f xs Prelude> map square [1..4] [1,4,9,16] There are many other commonly used HOFs such as foldl, foldr, and filter. By using function composition, point-free style notation, and HOFs, we can reduce the sum functions down to this implementation: sumSquares = sum . map (^2) sumCubes = sum.map (^3) © 2009–2016 Ravi Chugh, Stuart A. Kurtz
11
Polymorphism Review Haskell supports several kinds of polymorphism:
Polymorphism is the ability to implement expressions that can accept arguments and return results of different types without having to write variations on the same expression for each type. For example: What if we wanted to have addition for (+) all types of numbers: Integers, Fractional, Double, and Floats? Well if we did not have polymorphism we would have to write multiple functions that have the same addition code but is needed to handle various types: addFloat :: Float -> Float -> Float addInt :: Int -> Int -> Int addDouble :: Double -> Double -> Double Polymorphism makes programs easily extensible and facilitates rich libraries and common vocabularies.
12
Type Variables A type variable is a way to refer to an unspecified type or set of types in Haskell type signatures. They can be used to define polymorphic abstract data types: data Pair x y = Pair x y x and y are type variables (i.e. they can be represented by any concrete type): > Pair 1 "one" :: Pair Int String > Pair "one" 1.0 :: Pair String Double Type variables always begin with a lower-case letter.
13
Polymorphic functions
Polymorphic functions are functions whose types include type variables. For example: length [] = 0 length (_:as) = 1 + length as What is the the type of length? Could be: [Int] -> Int or [Double] -> Int, etc… But what is the type of length inferred by Haskell? > :t length length :: [a] -> Int This means that length can take as input a list over any type, and compute its length. What about the map function? > :t map map :: (a -> b) -> [a] -> [b]
14
Type Inference We are not required in Haskell to provide a type signature for every expression because it has type inference, which is an algorithm for determining the types of expressions. Haskell’s type inference is built on extended version of Damas- Hindley-Milner type system How it works: the compiler starts from the values whose types it knows and then works out the types of the other values. Type inference is useful when you want to figure out unfamiliar new code. You essentially don’t have to worry about the type but just experiment with it.
15
Typeclasses A typeclass is a another type of polymorphism that allows for expressing the faculties or interfaces that multiple datatypes may have in common. For example: numeric literals and their various types (Float, Double, Int, … ) implement a typeclass called Num, which defines a standard set of operators that can be used with any type of numbers. You may think typeclasses are analogous to Java or C++ classes… They are not! Typeclasses in Haskell are more analogous to the notion of interface from Java, or protocol from Objective-C or Swift.
16
TypeClass: Eq The ability to test values for equality is useful and can be done using the Eq type class. class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool This defines the type class Eq, which contains two functions: equality (==) and non-equality (/=). The declaration (class Eq a) says that any type a which is an instance of the Eq type class that must define the functions (==) and (/=).
17
Instances Here’s a very trivial example of defining an instance of a type class (i.e. Eq type class) from a programmer defined datatype: data Trivial = Trivial’ instance Eq Trivial where Trivial' == Trivial' = True What about (/=)? In the Eq documentation, you’ll want to note a particular bit of wording: Minimal complete definition: either == or /=. This tells you what methods you need to define to have a valid Eq instance. In this case, either (==) (equal) or (/=) (unequal) will suffice, as one can be defined as the negation of the other: class Eq a where (==), (/=) :: a -> a -> Bool x /= y = not (x == y) x == y = not (x /= y) This shows that you can define default implementation for your type classes!
18
Typeclasses (Cont’d.) You can retrieve information about a type class (e.g. type constraints and functions) using the :info command in the ghci: Prelude> :info Eq class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool
19
Example See “Days of the Week” Example in Lec3.hs
20
Type Constraints Typeclasses interact with the type system via type constraints, which require a type parameter to be of a particular type class. For example: elem is function defined in Data.List that determines whether a value occurs in a list and has the following type: > :t elem elem :: Eq a => a -> [a] -> Bool The (=>) indicates a type class constraint, where the type constraint means that elem can be used as type a -> [a] -> Bool whenever a is an instance of the Eq type class
21
Type Constraints Beginning with GHC 7.10, elem has an even more general type: > :t elem elem :: (Eq a, Foldable t) => a -> t a -> Bool Here the list type is an instance of the Foldable type class, and that if we need to include multiple constraints, they are "tupled."
22
Type Constraints & Data Types
When writing instances of typeclasses where a datatype contains type parameters, you’ll sometimes need to provide a type constraint in order to write an instance for that datatype. For example, the following code causes a problem (Why?) : data Identity a = Identity a instance Eq (Identity a) where (==) (Identity v) (Identity v') = v == v'
23
Type Constraints & Data Types
To fix this problem, we need to specify that type a is an instance of the Eq type class by adding a type constraint. instance Eq a => Eq (Identity a) where (==) (Identity v) (Identity v') = v == v'
24
Derived Instances One unique feature about Haskell is that it provides derived instances: data Pair x y = Pair x y instance (Eq x, Eq y) => Eq (Pair x y) where == (Pair x0 y0) (Pair x1 y1) = x0 == x1 && y0 == We don’t have to create a specialized instances for the type parameters of Pair because they are automatically derived from this single instance. We just require that the type parameters that Pair takes in are also instances of Eq.
25
Automatically Derived Instances
The derived instances pattern on the previous slide happens so much that Haskell will automatically generate instances (if it can) when applying the “deriving” keyword after the definition of the data type: data Pair x y = Pair x y deriving (Eq)
26
Number Types Numeric types are described by the Num class:
class Num a where (+), (-), (*) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a …. Num is the basis for a hierarchy of various numeric types: Int and Integer are instances of the Integral, which is a subclass of Num,. Float and Double are instances of the Fractional, which is a subclass of Num. Use the :info command to learn more about the numerical hierarchy
27
Ord Type Ord type class covers the types of things that can be put in order: class (Eq a) => Ord a where compare :: a -> a -> Ordering (<), (<=), (>), (>=) :: a -> a -> Bool max, min :: a -> a -> a … The documentation identifies the “compare” function as a minimal complete definition, meaning that default implementations are set up in a way such that all other members can be (and are) implemented in terms of the compare function.
28
Ord Examples Prelude> compare 7 8 LT Prelude> compare 4 4 EQ
Prelude> compare "Julie" "Chris" GT Prelude> compare True False Prelude> compare True True Prelude> max (3, 4) (2, 3) (3,4) Prelude> max "Julie" "Chris" "Julie"
29
Ord Example data DayOfWeek = Mon | Tue | Weds | Thu | Fri | Sat | Sun
deriving (Ord, Show) You can’t have an Ord instance unless you also have an Eq instance, so the compiler will complain if you don’t have both instances (Ord & Eq). Values to the left are “less than” values to the right, as if they were placed on a number line:
30
Enum Type This type class covers types that are enumerable (i.e. indexed from 0 to n-1):, therefore have known predecessors and successors. class Enum a where succ :: a -> a pred :: a -> a enumFromTo :: a -> a -> [a] … Examples: Prelude> succ 4 5 Prelude> pred 'd' 'c' Prelude> succ 4.5 5.5 Prelude> enumFromTo 'a' 'f' "abcdef"
31
show Type The Show class describes types whose values can be converted to Strings: class Show a where show :: a -> String … Example: data Pair x y = Pair x y deriving (Eq, Show) > Pair "Hello" 161 Pair "Hello" 161
32
Read Type This type class is essentially the opposite of Show. Where Show takes datatype values and turns them into human- readable strings, Read takes strings and turns them into datatype values. class Read a where read :: String -> a … The read function parses a String to produce a value of the given type. Both Read and Show type classes allows for interacting with the outside world (e.g. Strings from user input, the file system, and network requests). More on that later.
33
Set Comprehension Mathematicians have a concise notation for describing a set, which typically involves describing an initial set, a predicate, and a form for elements of that set. For example, the set of all squared even natural numbers: These mathematical sets are referred to as set comprehensions. Haskell has a similar concept called list comprehensions, which are a means of generating a new list from a list or lists. © 2009–2016 Ravi Chugh, Stuart A. Kurtz
34
List Comprehensions List comprehensions must have at least one list, called the generator, which represents the input list to the comprehension. Additionally, the comprehension can to determine which elements are drawn from the list and/or functions applied to those elements. Syntax : [ X^2 | x <- [1..10] ] X^2 => This is the output function that will apply to the members of the list we indicate. This can be any function that will be applied to each element in the generator list. | => The pipe here designates the separation between the output function and the input. X <- [ 1..10] =>This is the input set (i.e., a generator list) and a variable that represents the elements that will be drawn from that list. This says, “from a list of numbers from 1-10, take (<-) each element as an input to the output function.”
35
List Comprehensions Examples
-- From the Set Comprehension slide > [x^2 | x <- [1..10]] [1,4,9,16,25,36,49,64,81,100] > [x/2 | x <- [1..10]] [0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0]
36
predicates of List Comprehensions
Providing predicates (i.e. boolean-valued expression) allows you to limit the elements drawn from the generator list. Predicates are defined after the generator list and separated by commas. > [x*2 | x <- [1..20], x>= 12] [24,26,28,30,32,34,36,38,40] > [ x | x <- [10..20], x /= 13, x /= 15, x /= 19] [10,11,12,14,16,17,18,20]
37
Multiple Generators You can have list comprehension with multiple generators: Note that the rightmost generator will be exhausted first, then the second rightmost, and so on. > [ x*y | x <- [2,5,10], y <- [8,10,11]] [16,20,22,40,50,55,80,100,110] > [x^y | x <- [1..10], y <- [2, 3], x^y < 200] [1,1,4,8,9,27,16,64,25,125,36,49,64,81,100]
38
String List Comprehensions
Remember Strings are just a list of characters; therefore, they can also be used for creating list comprehensions: > [x | x <- "Three Letter Acronym", elem x ['A'..'Z']] "TLA"
39
newtype The newtype allows you to create a new data type similar to the data keyword BUT a type created with the newtype has exactly one constructor with exactly one field inside it newtype Identity a = Identity a newtype Pair a b = Pair (a, b)
40
Why Newtype? The newtype is almost exactly the same as using data. But newtype seems limiting… Why have it? Deconstructing or constructing a data-type incurs unnecessary run-time overhead. When using the newtype the compiler can optimize the generated code by not including explicit data constructor labels. Thus, use the newtype whenever you want to define a datatype with one constructor. If you’re really interested in knowing about the subtle differences between newtype and data checkout the link here:
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.