I read a recent Functional Pearl by Hinze and this inspired me to write up an example of projective programming and its motivation in logic/model theory.

The key to understanding the value of abstraction comes from logic and model theory: when you have more axioms, you have fewer models satisfying the axioms and the axioms prove more theorems; conversely, when you have fewer axioms you have more models and fewer theorems. This means that something more abstract can be in some sense simpler.

Let us consider an example in Haskell, viz.

concat :: [[a]] -> [a]

This can we written several ways:

concat :: [[a]] -> [a] concat [] = [] concat (x:xs) = x ++ concat xs

concat :: [[a]] -> [a] concat = foldr (++) []

concat :: [[a]] -> [a] concat = concatMap id

{-# LANGUAGE DeriveFunctor #-}

newtype Fix f = Fix { unFix :: f (Fix f) }

data ListF a b = Cons a b | Nil deriving (Functor)

cata :: Functor f => (f a -> a) -> (Fix f -> a) cata f = g where g = f . fmap g . unFix

fixList :: [a] -> Fix (ListF a) fixList [] = Fix Nil fixList (x:xs) = Fix (Cons x (fixList xs))

concat :: [[a]] -> [a] concat = cata a . fixList where a Nil = [] a (Cons x xs) = x ++ xs

By contrast,

join :: Monad m => m (m a) -> m a

is expressible only as

join :: Monad m => m (m a) -> m a join = (>>= id)

Note that join is a generalization of concat.

Despite the fact that join is more abstract, it is easier to come up with its implementation: we write the only sensible thing that typechecks.

This is not the case with our first implementation of concat; we could have easily written

concat :: [[a]] -> [a] concat [] = [] concat (x:xs) = concat xs ++ x

Coda

I believe programming in this style is a place where Haskell and its descendants have an advantage over other mainstream programming languages, notably due to typeclasses. I do not think this is the final word on abstraction by any means, but it does provide a poignant demonstration of the limits of non-functional programming languages.