Functors in Haskell
description
Transcript of Functors in Haskell
![Page 1: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/1.jpg)
1
Functors in Haskell
Adapted from material by Miran Lipovaca
![Page 2: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/2.jpg)
2
FunctorsFunctors 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 bOnly 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.
![Page 3: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/3.jpg)
3
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
![Page 4: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/4.jpg)
4
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.
![Page 5: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/5.jpg)
5
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.
![Page 6: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/6.jpg)
6
Using it: ghci> fmap (++ " HEY GUYS IM INSIDE THE JUST") (Just "Something serious.") Just "Something serious. HEY GUYS IM INSIDE THE JUST" ghci> fmap (++ " HEY GUYS IM INSIDE THE JUST") Nothing Nothing ghci> fmap (*2) (Just 200) Just 400 ghci> fmap (*2) Nothing Nothing
![Page 7: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/7.jpg)
7
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)
![Page 8: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/8.jpg)
8
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
)
![Page 9: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/9.jpg)
9
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.
![Page 10: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/10.jpg)
10
An example run:
ghci> let nums = [8,6,4,1,7,3,5] ghci> let numsTree = foldr treeInsert EmptyTree nums ghci> numsTree Node 5 (Node 3 (Node 1 EmptyTree EmptyTree) (Node 4 EmptyTree EmptyTree)) (Node 7 (Node 6 EmptyTree EmptyTree) (Node 8 EmptyTree EmptyTree))
![Page 11: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/11.jpg)
11
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)
![Page 12: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/12.jpg)
12
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 (Node 12 EmptyTree (Node 20 EmptyTree EmptyTree)))) EmptyTree
![Page 13: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/13.jpg)
13
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 :: * -> *
![Page 14: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/14.jpg)
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
![Page 15: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/15.jpg)
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
![Page 16: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/16.jpg)
Another functor: the <- in the IO classinstance 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 (++ “!”) getlineWill behave just like getline, but with a ! at the end of the line
![Page 17: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/17.jpg)
Example: program 1main = 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!"
![Page 18: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/18.jpg)
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]
![Page 19: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/19.jpg)
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!
![Page 20: Functors in Haskell](https://reader036.fdocuments.net/reader036/viewer/2022062323/56815931550346895dc66664/html5/thumbnails/20.jpg)
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.