This post is rendered from literate Haskell. I recommend doing the exercises inline, so use the source.

> {-# LANGUAGE DeriveFunctor > , DeriveFoldable > , DeriveTraversable > , TypeOperators #-} > > import Control . Applicative > import Data . Foldable > import Data . Traversable

Certain kinds of typeclasses have some very regular instances. For example, it is obvious how to implement (Num a, Num b) => Num (a,b) and (Monoid a, Monoid b) => Monoid (a,b) , and similarly if F is some applicative functor, (Num a) => Num (F a) and (Monoid a) => (Monoid F a) are obvious. Furthermore, these instances (and many others) seem to be obvious in the same way.

(+) a b = (+) <$> a <*> b mappend a b = mappend <$> a <*> b fromInteger n = pure (fromInteger n) mempty = pure mempty

And take them on pairs:

(x,x') + (y,y') = (x + y, x' + y') (x,x') `mappend` (y,y') = (x `mappend` y, x' `mappend` y') fromInteger n = (fromInteger n, fromInteger n) mempty = (mempty , mempty)

It would be straightforward for these cases to derive the necessary implementations from the type signature. However, it would be nice if there were a more abstract perspective, such that we didn’t have to inspect the type signature to find the operations – that they could arise from some other standard construction. Further, it is not quite as obvious from the the type signature how to automatically instantiate methods such as

mconcat :: (Monoid m) => [m] -> m

without making a special case for [] , whereas hopefully a more abstract perspective would inform us what kinds of type constructors would be supported.

In this post, we will see such an abstract perspective. It comes from (surprise!) category theory. I disclaim that I’m still a novice with category theory (but in the past few weeks I have gained competence by studying). So we will not get very deep into the theory, just enough to steal the useful concept and leave the rest behind. I welcome relevant insights from the more categorically educated in the comments.

F-Algebras

The unifying concept we will steal is the F-algebra. An F-algebra is a Functor f and a type a together with a function f a -> a . We can make this precise in Haskell:

> type Algebra f a = f a -> a

I claim that Num and Monoid instances are F-algebras over suitable functors. Look at the methods of Monoid :

mempty :: m mappend :: m -> m -> m

We need to find a functor f such that we can recover these two methods from a function of type f m -> m . With some squinting, we arrive at:

> data MonoidF m > = MEmpty > | MAppend m m > > memptyF :: Algebra MonoidF m -> m > memptyF alg = alg MEmpty > > mappendF :: Algebra MonoidF m -> ( m -> m -> m ) > mappendF alg x y = alg ( MAppend x y )

Exercise 1: work out the functor NumF over which Num instances are F-algebras, and write the methods of Num in terms of it.

Exercise 2: for each of the standard classes Eq , Read , Show , Bounded , and Integral , work out whether they are expressible as F-algebras. If so, give the functor; if not, explain or prove why not.

Exercise 3: write a function toMonoidAlg which finds the MonoidF -algebra for a given instance m of the Monoid class.

Combining Instances

Motivated by the examples in the introduction, we can find the “instance” for pairs given instances for each of the components.

> pairAlg :: ( Functor t ) => Algebra t a -> Algebra t b -> Algebra t ( a , b ) > pairAlg alga algb tab = ( alga ( fmap fst tab ) , algb ( fmap snd tab ) )

Also, we hope we can find the instance for an applicative functor given an instance for its argument

applicativeAlg :: (Functor t, Applicative f) => Algebra t a -> Algebra t (f a)

but there turns out to be trouble:

applicativeAlg alg tfa = ...

We need to get our hands on an t a somehow, and all we have is a t (f a) . This hints at something from the standard library:

sequenceA :: (Traversible t, Applicative f) => t (f a) -> f (t a)

which indicates that our functor needs more structure to implement applicativeAlg .

> applicativeAlg :: ( Traversable t , Applicative f ) > => Algebra t a -> Algebra t ( f a ) > applicativeAlg alg tfa = fmap alg ( sequenceA tfa )

Now we should be able to answer the query from the beginning:

Exercise 4: For what kinds of type constructors c is it possible to automatically derive instances for (a) pairs and (b) Applicative s for a typeclass with a method of type c a -> a . (e.g. mconcat :: [a] -> a ). Demonstrate this with an implementation.

Combining Classes

Intuitively, joining the methods of two classes which are both expressible as F-algebras should give us another class expressible as an F-algebra. This is demonstrated by the following construction:

> data ( f :+: g ) a = InL ( f a ) | InR ( g a ) > deriving ( Functor , Foldable , Traversable ) > > coproductAlg :: ( Functor f , Functor g ) > => Algebra f a -> Algebra g a -> Algebra ( f :+: g ) a > coproductAlg falg _ ( InL fa ) = falg fa > coproductAlg _ galg ( InR ga ) = galg ga

So now we can model a subclass of both Num and Monoid by type NumMonoidF = NumF :+: MonoidF .

Exercise 5: We hope to be able to recover Algebra NumF a from Algebra NumMonoidF a , demonstrating that the latter is in fact a subclass. Implement the necessary function(s).

Exercise 6: Given the functor product definition

> data ( f :*: g ) a = Pair ( f a ) ( g a ) > deriving ( Functor , Foldable , Traversable )

find a suitable combinator for forming algebras over a product functor. It may not have the same form as coproduct’s combinator! What would a typeclass formed by a product of two typeclasses interpreted as F-algebras look like?

Free Constructions

One of the neat things we can do with typeclasses expressed as F-algebras is form free monads over them – i.e. form the data type of a “syntax tree” over the methods of a class (with a given set of free variables). Begin with the free monad over a functor:

> data Free f a > = Pure a > | Effect ( f ( Free f a ) ) > deriving ( Functor , Foldable , Traversable ) > > instance ( Functor f ) => Monad ( Free f ) where > return = Pure > Pure a >>= t = t a > Effect f >>= t = Effect ( fmap ( >>= t ) f )

(Church-encoding this gives better performance, but I’m using this version for expository purposes)

Free f a can be interpreted as a syntax tree over the typeclass formed by f with free variables in a . This is also called an “initial algebra”, a term you may have heard thrown around in the Haskell community from time to time. We demonstrate that a free construction over a functor is a valid F-algebra for that functor:

> initialAlgebra :: ( Functor f ) => Algebra f ( Free f a ) > initialAlgebra = Effect

And that it is possible to “interpret” an initial algebra using any other F-algebra over that functor.

> initiality :: ( Functor f ) => Algebra f a -> Free f a -> a > initiality alg ( Pure a ) = a > initiality alg ( Effect f ) = alg ( fmap ( initiality alg ) f )

Exercise 7: Give a monoid isomorphism (a bijection that preserves the monoid operations) between Free MonoidF and lists [] , ignoring that Haskell allows infinitely large terms. Then, using an infinite term, show how this isomorphism fails.

Next time: F-Coalgebras