A Functor in Haskell is a type of kind f :: * -> *, which supports the operation fmap :: (a -> b) -> f a -> f b. Many "container" types are Functors, including the List type. But we’re not going to talk about "containers", per se. We’re going to explore a few of the simplest functors that we can squeeze out of the Haskell type system. Of course, I don’t know the actual name of some of these, so you’ll have to forgive me for giving them pet names instead.

Our end goal in exporing these functors is to reproduce the Conduit library by assembling pieces of it, one functor at a time. For this post, we’re just going to survey various functors, and ways to compose them. I’ll also touch lightly on how they play with the Free Monad Transformer, though serious discussion of such will be left to later posts.

The Identity functor

The Identity functor trivially wraps a value. In order to implement fmap, it just applies the function directly to the value inside.

>instanceFunctorIdentitywhere>fmapf(Identitynext)=Identity(fnext)

When used with the Free Monad Transformer, the Identity monad trivially grants you the ability to represent "the thing that comes next". This will be convenient for us sometime around part 5 of this series.

The Empty functor

>dataEmptynext=Empty

The Empty functor contains no information. It admits the type variable, but is otherwise nothing but the trivial value, ().

>instanceFunctorEmptywhere>fmap_fEmpty=Empty

When used with the Free Monad Transformer, the Empty functor allows you to short-circuit computation. The Free Monad Transformer works by stacking functors up, but as you can see, the Empty functor has no room for other functors to live inside of it.

The Const functor

>newtypeConstanext=Consta

The Const functor is very similar to the Empty functor, except that it contains some actual value, which remains untouched by functor operations.

>instanceFunctor(Consta)where>fmap_f(Consta)=Consta

When used with the Free Monad Transformer, the Const functor allows you to terminate computation while providing some information. Joining this functor with the Identity functor will allow us to supply information without terminating computation (because the Identity functor gives a space for the "next" computation), which will be the heart of our yield functionality.

The (a ->) functor

>newtypeFunanext=Fun(a->next)

Functions, as you may know, are functors. In order to fmap onto a function, simply wait until the function has acquired input and produced an output, and then map onto the function’s output.

>instanceFunctor(Funa)where>fmapf(Fung)=Fun(\x->f(gx))

When used with the Free Monad Transformer, this allows us to represent inversion of control, or acquiring information from some outside source, in order to determine what to do next. This will be the heart of our await functionality.

Composing functors

I won’t be using this particular form of functor composition for future posts, but it was worth noting. Instead, let’s take a look at two other ways to combine functors:

Combining functors (tagged union)

>infixl5:|:>data(f:|:g)x=L(fx)|R(gx)

If I have two functors f and g, then their tagged union is also a functor. We can just tag the f x values with L and the g x values with R so that whenever we come across some data, we know which of the two functors it actually was.

By case analysis, we can make a tagged union of functors also be a functor: