There is a branch of mathematics — category theory — and it fits what we are discussing like nothing else, so we simply have to talk about it a little. The idea at its core is defiantly simple: forget what lies inside mathematical structures and look only at the connections between them. The theory works with just two kinds of entities. The first are objects: ; what they actually are, the category does not care. The second are morphisms, also known as arrows: each has a source and a target, .
Arrows can be glued together: for example, for and the composition is defined. Then come two axioms. First: composition is associative, . Second: every object has an identity arrow , neutral with respect to composition: . Objects, arrows and two axioms — that is what a category is.
In Haskell this pattern is captured in a type class — from the module:
class Category cat where
id :: cat a a
(.) :: cat b c -> cat a b -> cat a c
-- the class laws are exactly the category axioms:
f . id == f
id . f == f
(f . g) . h == f . (g . h)Here is the arrow itself, parameterised by source and target (kind ). Frankly, the most all-embracing instance of a category is : objects are Haskell types, morphisms are functions between them, composition is from the Prelude, the identity is :
instance Category (->) where
id x = x
(g . f) x = g (f x)
show :: Int -> String -- a morphism Int -> String
length :: String -> Int -- a morphism String -> Int
length . show :: Int -> Int -- their composition is a morphism tooThe real value of category theory for us is that every construction we discussed in this chapter can be stated in its language very cleanly and crisply:
- monoid — a category with a single object: the morphisms are the elements of the monoid, is the composition, is the identity;
- functor — a mapping from category to category: objects go to objects, arrows go to arrows, and identity and composition are preserved (these are exactly the laws); our functors map into itself, which is why they are called endofunctors — the prefix “endo-” is Greek for “within”: a functor from a category into that very category;
- applicative functor — a monoidal functor: besides arrows it also carries pairs across, glues into , and provides the unit;
- monad — the category of Kleisli arrows with composition and identity ;
- Foldable — a fold into a category with a single object: carries the elements of the structure into a monoid and glues them there;
- Traversable — swapping two endofunctors around: .
The point about the monad can be touched with your hands: the Kleisli category is a newtype wrapper with a instance:
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
instance Monad m => Category (Kleisli m) where
id = Kleisli pure
Kleisli g . Kleisli f = Kleisli (f >=> g)
-- the familiar half — now as a Kleisli arrow:
half :: Kleisli Maybe Int Int
half = Kleisli (\x -> if even x then Just (x `div` 2) else Nothing)
quarter :: Kleisli Maybe Int Int
quarter = half . half -- an ordinary dot glued effectful arrows together
runKleisli quarter 12 -- Just 3
runKleisli quarter 6 -- Nothing (3 is odd — the second halving failed)Checking the laws for this instance means checking the monad laws: the identity and the associativity of we have already seen in the section on Kleisli arrows. A monad and its Kleisli category can be recovered from each other: they are one construction written down in two ways.
We arrived at Haskell along the path of the λ-calculus: terms, reductions, types. But one could come from the other side as well: the typed λ-calculus and cartesian closed categories are one and the same subject in two languages. Then types are objects, programs are morphisms, and the monoids, functors and monads of this chapter are not Haskell inventions but notions of category theory, carried over into code almost verbatim.