Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do functors work in haskell?

I'm trying to learn Haskell and I'm through all the basics. But now I'm stuck, trying to get my head around functors.

I've read that "A functor transforms one category into another category". What does this mean?

I know it's a lot to ask, but could anyone give me a plain english explanation of functors or maybe a simple use case?

like image 692
Matias Rasmussen Avatar asked Oct 30 '12 08:10

Matias Rasmussen


People also ask

Is maybe a functor Haskell?

Another simple example of a functor is the Maybe type. This object can contain a value of a particular type as Just , or it is Nothing (like a null value).

Is functor a Typeclass?

The Functor typeclass represents the mathematical functor: a mapping between categories in the context of category theory. In practice a functor represents a type that can be mapped over.

What is an applicative in Haskell?

In Haskell, an applicative is a parametrized type that we think of as being a container for data of that type plus two methods pure and <*> . Consider a parametrized type f a . The pure method for an applicative of type f has type. pure :: a -> f a. and can be thought of as bringing values into the applicative.


2 Answers

I've accidently written a

Haskell Functors Tutorial

I'll answer your question using examples, and I'll put the types underneath in comments.

Watch out for the pattern in the types.

fmap is a generalisation of map

Functors are for giving you the fmap function. fmap works like map, so let's check out map first:

map (subtract 1) [2,4,8,16] = [1,3,7,15] --    Int->Int     [Int]         [Int] 

So it uses the function (subtract 1) inside the list. In fact, for lists, fmap does exactly what map does. Let's multiply everything by 10 this time:

fmap (* 10)  [2,4,8,16] = [20,40,80,160] --  Int->Int    [Int]         [Int] 

I'd describe this as mapping the function that multiplies by 10 over the list.

fmap also works on Maybe

What else can I fmap over? Let's use the Maybe data type, which has two types of values, Nothing and Just x. (You can use Nothing to represent a failure to get an answer while Just x represents an answer.)

fmap  (+7)    (Just 10)  = Just 17 fmap  (+7)     Nothing   = Nothing --  Int->Int  Maybe Int    Maybe Int 

OK, so again, fmap is using (+7) inside the Maybe. And we can fmap other functions too. length finds the length of a list, so we can fmap it over Maybe [Double]

fmap    length             Nothing                      = Nothing fmap    length    (Just [5.0, 4.0, 3.0, 2.0, 1.573458]) = Just 5 --  [Double]->Int         Maybe [Double]                  Maybe Int 

Actually length :: [a] -> Int but I'm using it here on [Double] so I specialised it.

Let's use show to turn stuff into strings. Secretly the actual type of show is Show a => a -> String, but that's a bit long, and I'm using it here on an Int, so it specialises to Int -> String.

fmap  show     (Just 12)  = Just "12" fmap  show      Nothing   = Nothing -- Int->String  Maybe Int   Maybe String 

also, looking back to lists

fmap   show     [3,4,5] = ["3", "4", "5"] -- Int->String   [Int]       [String] 

fmap works on Either something

Let's use it on a slightly different structure, Either. Values of type Either a b are either Left a values or Right b values. Sometimes we use Either to represent a success Right goodvalue or failure Left errordetails, and sometime just to mix together values of two types into one. Anyway, the functor for the Either data type only works on the Right - it leaves Left values alone. That makes sense particularly if you're using Right values as the successful ones (and in fact we wouldn't be able to make it work on both because the types aren't necessarily the same). Lets use the type Either String Int as an example

fmap (5*)      (Left "hi")     =    Left "hi" fmap (5*)      (Right 4)       =    Right 20 -- Int->Int  Either String Int   Either String Int 

It makes (5*) work inside the Either, but for Eithers, only the Right values get changed. But we can do it the other way round on Either Int String, as long as the function works on strings. Let's put ", cool!" at the end of stuff, using (++ ", cool!").

fmap (++ ", cool!")          (Left 4)           = Left 4 fmap (++ ", cool!") (Right "fmap edits values") = Right "fmap edits values, cool!" --   String->String    Either Int String          Either Int String 

It's especially cool to use fmap on IO

Now one of my favourite ways of using fmap is to use it on IO values to edit the value some IO operation gives me. Let's make an example that lets you type something in and then prints it out straight away:

echo1 :: IO () echo1 = do     putStrLn "Say something!"     whattheysaid <- getLine  -- getLine :: IO String     putStrLn whattheysaid    -- putStrLn :: String -> IO () 

We can write that in a way that feels neater to me:

echo2 :: IO () echo2 = putStrLn "Say something"          >> getLine >>= putStrLn 

>> does one thing after another, but the reason I like this is because >>= takes the String that getLine gave us and feeds it to putStrLn which takes a String. What if we wanted to just greet the user:

greet1 :: IO () greet1 = do     putStrLn "What's your name?"     name <- getLine     putStrLn ("Hello, " ++ name) 

If we wanted to write that in the neater way I'm a bit stuck. I'd have to write

greet2 :: IO () greet2 = putStrLn "What's your name?"           >> getLine >>= (\name -> putStrLn ("Hello, " ++ name)) 

which is not nicer than the do version. In fact the do notation is there so you don't have to do this. But can fmap come to the rescue? Yes it can. ("Hello, "++) is a function that I can fmap over the getLine!

fmap ("Hello, " ++)  getLine   = -- read a line, return "Hello, " in front of it --   String->String  IO String    IO String 

we can use it like this:

greet3 :: IO () greet3 = putStrLn "What's your name?"           >> fmap ("Hello, "++) getLine >>= putStrLn 

We can pull this trick on anything we're given. Let's disagree with whether "True" or "False" was typed in:

fmap   not      readLn   = -- read a line that has a Bool on it, change it --  Bool->Bool  IO Bool       IO Bool 

Or let's just report the size of a file:

fmap  length    (readFile "test.txt") = -- read the file, return its length --  String->Int      IO String              IO Int --   [a]->Int        IO [Char]              IO Int     (more precisely) 

Conclusions: What does fmap do, and what does it do it to?

If you've been watching the patterns in the types and thinking about the examples you'll have noticed that fmap takes a function that works on some values, and applies that function on something that has or produces those values somehow, editing the values. (eg readLn was to read Bool, so had type IO Bool there's a Boolean value in it in the sense that it produces a Bool, eg2 [4,5,6] has Ints in it.)

fmap :: (a -> b) -> Something a -> Something b 

this works for Something being List-of (written []), Maybe, Either String, Either Int, IO and loads of over things. We call it a Functor if this works in a sensible way (there are some rules - later). The actual type of fmap is

fmap :: Functor something => (a -> b) -> something a -> something b 

but we usually replace something with f for brevity. It's all the same to the compiler, though:

fmap :: Functor f => (a -> b) -> f a -> f b 

Have a look back at the types and check this always works - think about Either String Int carefully - what's f that time?

Appendix: What are the Functor rules, and why do we have them?

id is the identity function:

id :: a -> a id x = x 

Here are the rules:

fmap id  ==  id                    -- identity identity fmap (f . g)  ==  fmap f . fmap g  -- composition 

Firstly the identity identity: If you map the function that does nothing, that doesn't change anything. That sounds obvious (a lot of rules do), but you can interpret that as saying that fmap is only allowed to change the values, not the structure. fmap isn't allowed to turn Just 4 into Nothing, or [6] into [1,2,3,6], or Right 4 into Left 4 because more than just the data changed - the structure or context for that data changed.

I hit this rule once when I was working on a graphical user interface project - I wanted to be able to edit the values, but I couldn't do it without changing the structure underneath. No-one would have really noticed the difference because it had the same effect, but realising it didn't obey the functor rules made me rethink my whole design, and it's much cleaner, slicker and faster now.

Secondly the composition: this means you can choose whether to fmap one function at a time, or fmap them both at the same time. If fmap leaves the structure/context of your values alone and just edits them with the function its given, it will work with this rule too.

There's a secret third rule that mathematicians have, but we don't call it a rule in Haskell, because it just looks like a type declaration:

fmap :: (a -> b) -> something a -> something b 

This one stops you from applying the function to just the first value in your list, for example. This law is enforced by the compiler.

Why do we have them? To make sure fmap doesn't sneakily do anything behind the scenes or change anything we didn't expect. They're not enforced by the compiler (asking the compiler to prove a theorem before it compiles your code isn't fair, and would slow compilation down - the programmer should check). This means you can cheat the laws a bit, but that's a bad plan because your code can give unexpected results.

The laws of Functor are to make sure that fmap applies your function fairly, equally, everywhere and without any other changes. That's a good, clean, clear, reliable, reusable thing.

like image 176
AndrewC Avatar answered Nov 09 '22 13:11

AndrewC


A fuzzy explanation would be that a Functor is some sort of container and an associated function fmap that allows you to alter whatever is contained, given a function that transforms the contained.

For instance, lists are this kind of container, such that fmap (+1) [1,2,3,4] yields [2,3,4,5].

Maybe can also be made a functor, such that fmap toUpper (Just 'a') yields Just 'A'.

The general type of fmap shows quite neatly what is going on:

fmap :: Functor f => (a -> b) -> f a -> f b 

And the specialized versions may make it clearer. Here's the list version:

fmap :: (a -> b) -> [a] -> [b] 

And the Maybe version:

fmap :: (a -> b) -> Maybe a -> Maybe b 

You can gain information on the standard Functor instances by querying GHCI with :i Functor and many modules define more instances of Functors (and other type classes.)

Please don't take the word "container" too seriously, though. Functors are a well-defined concept, but you can often reason about it with this fuzzy analogy.

Your best bet in understanding what is going on is simply reading the definition of each of the instances, which should give you an intuition on what is going on. From there it's only a small step to really formalize your understanding of the concept. What needs to be added is a clarification of what our "container" really is, and that each instance much satisfy a pair of simple laws.

like image 27
Sarah Avatar answered Nov 09 '22 15:11

Sarah