functors in haskell

Post on 02-Feb-2016

126 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

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

1

Functors in Haskell

Adapted from material by Miran Lipovaca

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.

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   

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.

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.

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     

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)

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

)  

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.

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))   

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)   

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    

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 :: * -> *      

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        

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    

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

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!"          

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]          

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!

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.

top related