Download presentation
Presentation is loading. Please wait.
Published byRoss Elliott Modified over 9 years ago
1
0 Functors in Haskell Adapted from material by Miran Lipovaca
2
1 Functors Functors are a typeclass, just like Ord, Eq, Show, and all the others. This one is designed to hold things that can be mapped over; for example, lists are part of this typeclass. class Functor f where fmap :: (a -> b) -> f a -> f b Only one typeclass method, called fmap. Basically, says: give me a function that takes a and returns b and a “box” with type a inside of it, and I’ll return a “box” with type b inside of it.
3
2 Compare fmap to map: fmap :: (a -> b) -> f a -> f b map :: (a -> b) -> [a] -> [b] So map is a lot like a functor! Here, map takes a function and a list of type a, and returns a list of type b. In fact, can define map in terms of fmap: instance Functor [] where fmap = map
4
3 Notice what we wrote: instance Functor [] where fmap = map We did NOT write “instance Functor [a] where…”, since f has to be a type constructor that takes one type. Here, [a] is already a concrete type, while [] is a type constructor that takes one type and can produce many types, like [Int], [String], [[Int]], etc.
5
4 Another example: instance Functor Maybe where fmap f (Just x) = Just (f x) fmap f Nothing = Nothing Again, we did NOT write “instance Functor (Maybe m) where…”, since functor wants a type constructor. Mentally replace the f’s with Maybe, so fmap acts like (a -> b) -> Maybe a -> Maybe b. If we put (Maybe m), would have (a -> b) -> (Maybe m) a -> (Maybe m) b, which looks wrong.
6
5 Using it: ghci> fmap (++ " HEY GUYS IM INSIDE THE JUST") (Just "Something serious.") Just "Something serious. HEY GUYS IM INSIDE TH E JUST" ghci> fmap (++ " HEY GUYS IM INSIDE THE JUST") Nothing Nothing ghci> fmap (*2) (Just 200) Just 400 ghci> fmap (*2) Nothing Nothing
7
6 Another example - the tree class. We’ll define a tree to be either: - an empty tree - an element with a value and two (sub)trees data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq) This is slightly different than our last definition. Here, trees will be of the form: Node 5 (Node 3 (Node 1 EmptyTree EmptyTree) (Node 4 EmptyTree EmptyTree)) (Node 6 EmptyTree EmptyTree)
8
7 Some tree functions: singleton :: a -> Tree a singleton x = Node x EmptyTree EmptyTree treeInsert :: (Ord a) => a -> Tree a -> Tree a treeInsert x EmptyTree = singleton x treeInsert x (Node a left right) | x == a = Node x left right | x < a = Node a (treeInsert x left) right | x > a = Node a left (treeInsert x right)
9
8 And now to find an element in the tree: treeElem :: (Ord a) => a -> Tree a -> Bool treeElem x EmptyTree = False treeElem x (Node a left right) | x == a = True | x < a = treeElem x left | x > a = treeElem x right Note: we deliberately assumed we could compare the elements of the nodes so that we could make this a binary search tree. If this is an “unordered” tree, would need to search both left and right subtrees.
10
9 An example run: ghci> let nums = [8,6,4,1,7,3,5] ghci> let numsTree = foldr treeInsert EmptyTre e nums ghci> numsTree Node 5 (Node 3 (Node 1 EmptyTree EmptyTree) (N ode 4 EmptyTree EmptyTree)) (Node 7 (Node 6 Em ptyTree EmptyTree) (Node 8 EmptyTree EmptyTre e))
11
10 Back to functors: If we looked at fmap as though it were only for trees, it would look something like: (a -> b) -> Tree a -> Tree b We can certainly phrase this as a functor, also: instance Functor Tree where fmap f EmptyTree = EmptyTree fmap f (Node x leftsub rightsub) = Node (f x) (fmap f leftsub) (fmap f rightsub)
12
11 Using the tree functor: ghci> fmap (*2) EmptyTree EmptyTree ghci> fmap (*4) (foldr treeInsert EmptyTree [5,7,3,2,1,7]) Node 28 (Node 4 EmptyTree (Node 8 EmptyTree (N ode 12 EmptyTree (Node 20 EmptyTree EmptyTree )))) EmptyTree
13
12 Some rules: -The type has to have a kind of * -> *, which means it takes only 1 concrete type as a parameter. Example: ghci> :k Maybe Maybe :: * -> *
14
If a type takes 2 parameters, like Either: data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show) ghci> Right 20 Right 20 ghci> :t Right 'a' Right 'a' :: Either a Char ghci> :t Left True Left True :: Either Bool b
15
Then the signature :k looks like this: ghci> :k Either Either :: * -> * -> * For this situation, we need to partially apply the type constructor until it takes only 1 input. Remember, Haskell is good at partial evaluations! instance Functor (Either a) where fmap :: (b -> c) -> Either a b -> Either a c
16
Another functor: the <- in the IO class instance Functor IO where fmap f action = do result <- action return (f result) Comments: -The result of an IO action must be an IO action, so we’ll use do to glue our 2 things together. - We can use this to make our code more compact, since we won’t need to explicitly “unpack” the IO. - Now (for example) the command: fmap (++ “!”) getline Will behave just like getline, but with a ! at the end of the line
17
Example: program 1 main = do line <- getLine let line' = reverse line putStrLn $ "You said " ++ line' ++ " backwards!" With fmap: main = do line <- fmap reverse getLine putStrLn $ "You said " ++ line ++ " backwards!"
18
There are two rules that every functor should obey. Note that these aren’t enforced by Haskell, but they are important! First: If we map the identity function over a functor, then the functor we get back should be the same as the original functor. So: fmap id = id. ghci> fmap id (Just 3) Just 3 ghci> id (Just 3) Just 3 ghci> fmap id [1..5] [1,2,3,4,5] ghci> id [1..5] [1,2,3,4,5]
19
Second rule: composing two functions and then mapping the resulting function over a functor should be the same as first mapping one function over the functor and then mapping the other. So: fmap (f. g) = fmap f. fmap g. Huh? Well, fmap (f. g) (Just x) is (if you go back and look) defined as Just ((f. g) x), which is the same as Just (f (g x)), since we ’ re just composing functions. And fmap f (fmap g (Just x)) is fmap f (Just (g x)) which is then Just (f (g x)), so we ’ re ok!
20
Now why do we care? If we know that a type obeys both laws, we can make certain assumptions about how it will act. If a type obeys the functor laws, we know that calling fmap on a value of that type will only map the function over it, nothing more. This leads to code that is more abstract and extensible. All the Functor examples in Haskell obey these laws by default.
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.