1 / 20

Functors in Haskell

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

kirti
Download Presentation

Functors in Haskell

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Functors in Haskell Adapted from material by Miran Lipovaca

  2. 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. classFunctorfwhere fmap::(a->b)->fa->fb 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. 1

  3. Compare fmap to map: fmap::(a->b)->fa->fb 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: instanceFunctor[]where fmap=map 2

  4. Notice what we wrote: instanceFunctor[]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. 3

  5. Another example: instanceFunctorMaybewhere fmapf(Justx)=Just(fx) fmapfNothing=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. 4

  6. Using it: ghci>fmap(++"HEYGUYSIMINSIDETHE JUST")(Just"Somethingserious.") Just"Somethingserious.HEYGUYSIMINSIDETHEJUST" ghci>fmap(++"HEYGUYSIMINSIDETHE JUST")Nothing Nothing ghci>fmap(*2)(Just200) Just400 ghci>fmap(*2)Nothing Nothing 5

  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 dataTreea=EmptyTree| Nodea(Treea)(Treea) 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) 6

  8. Some tree functions: singleton::a->Treea singletonx=NodexEmptyTreeEmptyTree treeInsert::(Orda)=>a->Treea->Treea treeInsertxEmptyTree=singletonx treeInsertx(Nodealeftright) |x==a=Nodexleftright |x<a=Nodea(treeInsertxleft)right |x>a=Nodealeft(treeInsertxright) 7

  9. And now to find an element in the tree: treeElem::(Orda)=>a->Treea->Bool treeElemxEmptyTree=False treeElemx(Nodealeftright) |x==a=True |x<a=treeElemxleft |x>a=treeElemxright 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. 8

  10. An example run: ghci>letnums=[8,6,4,1,7,3,5] ghci>letnumsTree=foldrtreeInsertEmptyTreenums ghci>numsTree Node5(Node3(Node1EmptyTreeEmptyTree)(Node4EmptyTreeEmptyTree))(Node7(Node6EmptyTreeEmptyTree)(Node8EmptyTreeEmptyTree)) 9

  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: instanceFunctorTreewhere fmapfEmptyTree=EmptyTree fmapf(Nodexleftsubrightsub)= Node(fx)(fmapfleftsub) (fmapfrightsub) 10

  12. Using the tree functor: ghci>fmap(*2)EmptyTree EmptyTree ghci>fmap(*4)(foldrtreeInsert EmptyTree[5,7,3,2,1,7]) Node28(Node4EmptyTree(Node8EmptyTree(Node12EmptyTree(Node20EmptyTreeEmptyTree))))EmptyTree 11

  13. Some rules: • The type has to have a kind of * -> *, which means it takes only 1 concrete type as a parameter. Example: ghci>:kMaybe Maybe::*->* 12

  14. If a type takes 2 parameters, like Either: dataEitherab=Lefta |Rightb deriving(Eq,Ord,Read,Show) ghci>Right20 Right20 ghci>:tRight'a' Right'a'::EitheraChar ghci>:tLeftTrue LeftTrue::EitherBoolb

  15. Then the signature :k looks like this: ghci>:kEither 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 instanceFunctorIOwhere fmapfaction=do result<-action return(fresult) • 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=doline<-getLine letline'=reverseline putStrLn$"Yousaid"++line'++ "backwards!" With fmap: main=doline<-fmapreversegetLine putStrLn$"Yousaid"++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>fmapid(Just3) Just3 ghci>id(Just3) Just3 ghci>fmapid[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.

More Related