Learning Haskell – Functors

Disclaimer: This content comes out of the fantastic book Effective Haskell written by Rebecca Skinner. I was only talking notes and doing my own little experiments. Original Haskell content by me will follow shortly.

I am learning Haskell and I am having great fun with it. My goal is to be able to apply Denotational Design as described by Conal Elliott to the software I build and learning Haskell gives me a better understanding of the fundamentals, through practice.

I believe that the programming language you work in influences the way you think, so I make a real effort to be fluent in multiple languages with different paradigms.

Functor

For the mathematical definition of functor see Wikipedia.

In programming a functor is a thing that knows how to apply a function to a certain structure preserving that structure. The functor does this via the fmap function that the typeclass Functor describes:

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

Read this as: With a function from a to b and a functor of a it returns a functor of b.

There is another function <$ in the functor typeclass that is called replace:

(<$) :: a -> f b -> f a

Imagine the function you want to apply to the functor always returns the same value then the signature becomes clear: With a constant value a and a functor of b it returns a functor of a.

This can always be expressed with fmap giving it const as it's function.

Let build our own Functor typeclass in Haskell:

class MyFunctor f where myFMap :: (a -> b) -> f a -> f b myReplace :: a -> f b -> f a myReplace a f = myFMap (const a) f

As mentioned myReplace is already implemented for every instance of out MyFunctor class.

List

To see this in action let's build something simple as our structure like a list:

data MyList a = Empty | MyList a (MyList a) deriving Show

This is your standard recursive list data structure. Let's make it an instance of MyFunctor:

instance MyFunctor MyList where myFMap _ Empty = Empty myFMap f (MyList a rest) = MyList (f a) (myFMap f rest)

There you have it. Now let's invent infix operators for myFMap and myReplace and try them out:

infixl 4 <.> (<.>) = myFMap infixl 4 <. (<.) = myReplace (+1) <.> MyList 1 Empty -- Mylist 2 Empty 99 <. Empty -- Empty 99 <. MyList 1 (MyList 2 Empty) -- MyList 99 (MyList 99 Empty)

So you see the Functor instance of our MyList can apply a function to every element preserving the structure of the MyList. The function can change the type of the element but it will always be a list with the same number of elements:

show <.> MyList 1 Empty -- MyList "1" Empty

Maybe

Let's build another structure that can implement the Functor typeclass. I will use German words for the type name and the constructors because, just as List and Maybe are already instances of Functor, and although we will implement MyFunctor, I think this approach makes it clearer:

data Vielleicht a = Nix | Ein a deriving Show instance MyFunctor Vielleicht where myFMap _ Nix = Nix myFMap f (Ein a) = Ein (f a)

Since MyList and Vielleicht are of the same kind: * -> * that means both taking one parameter to construct a type this was pretty mechanic. Nothing new.

Either

I am using German words again to build an Either type that has the kind: * -> * -> *. This means the type constructor will need two parameters to construct a type.

data Entweder a b = Links a | Rechts b deriving show

We can use that to build a save division function:

teile :: (Eq a, Fractional a) => a -> a -> Entweder String a teile a b | b == 0 = Links "NaN" | otherwise = Rechts $ a / b

Now let's think about the Functor instance for Entweder. I might want to pass round to the result of teile but that only needs to be done if the result is an instance of Rechts. I don't want to round the String.

So we can implement a sane Functor instance like this:

instance MyFunctor (Entweder a) where myFMap _ (Links a) = (Links a) myFMap f (Rechts b) = Rechts $ f a

Let's try it out:

round <.> teile 5 2 -- Rechts 2

Functions

Rebecca shows something else that is cool. Let's create a wrapper for a function so that we can show that even a function can be a functor:

newtype Funktion a b = Funktion { run :: (a -> b) } instance MyFunctor (Funktion a) where myFMap f (Funktion g) = Funktion (f . g) run (show <.> Funktion (+1)) 12 -- "13" the type is String, now

Type :i Functor in ghci and you can see that (->) r is indeed a Functor instance. In hindsight this seems obvious. The functor knows how to apply a function f to the result of the function g and gives you a new function f after g – thus the structure is maintained.