functors in haskell

20
1 Functors in Haskell Adapted from material by Miran Lipovaca

Upload: kirti

Post on 02-Feb-2016

123 views

Category:

Documents


0 download

DESCRIPTION

Functors in Haskell. Adapted from material by Miran Lipovaca. 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 - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Functors  in Haskell

1

Functors in Haskell

Adapted from material by Miran Lipovaca

Page 2: Functors  in Haskell

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.