- 82 Views
- Uploaded on
- Presentation posted in: General

Functors in Haskell

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.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.

- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Functors

in Haskell

Adapted from material by Miran Lipovaca

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

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

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

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

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

- 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

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

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

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

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

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

- 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

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

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

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

Example: program 1

main=doline<-getLine

letline'=reverseline

putStrLn$"Yousaid"++line'++

"backwards!"

With fmap:

main=doline<-fmapreversegetLine

putStrLn$"Yousaid"++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>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]

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.