
The Algebra of Algebraic Data Types - crntaylor
http://chris-taylor.github.com/blog/2013/02/10/the-algebra-of-algebraic-data-types/
======
flebron
If you want to see how crazy this can get, know that you can actually take the
derivative of a type (with respect to another type), and this yields useful
information.

See McBride's paper <http://strictlypositive.org/diff.pdf> , or this guy who
is discovering it by himself:
[http://stackoverflow.com/questions/9190352/abusing-the-
algeb...](http://stackoverflow.com/questions/9190352/abusing-the-algebra-of-
algebraic-data-types-why-does-this-work)

~~~
crntaylor
"This guy who is discovering it by himself" is me (the OP) about a year ago!

Derivatives of types to come in part II or III (or just watch the talk on
YouTube: <http://www.youtube.com/watch?v=YScIPA8RbVE>)

------
batterseapower
Pedants would add that the analogy with number of inhabitants is all a little
screwed up in Haskell since bottom is an implicit inhabitant of all data
types, which means that e.g. Either Void () has a different number of
inhabitants than () and so they cannot strictly speaking be made isomorphic.

Strict languages can sidestep this problem to an extent but still end up with
"wrong" numbers of inhabitants for e.g. T -> (), since (\\_ -> ()) is
different from (\\_ -> undefined).

So the only guys living in pure algebraic bliss are the total functional
programmers like the Agda aficionados.

~~~
crntaylor
Yup. I deliberately ignored ⊥ to simplify the presentation (and I make no
apologies for doing so!)

There are a couple of posts by Dan Piponi where he explores how to count
inhabitants of types when you properly account for ⊥:

    
    
        http://blog.sigfpe.com/2008/02/how-many-functions-are-there-from-to.html
    

He does it by counting the number of functions () -> () (where () is of course
inhabited by both () and ⊥), and concludes that the number of functions () ->
() depends on the semantics of the language:

    
    
        1 in a total language (like Agda) or in mathematics.
        3 in a lazy language (like Haskell) when working completely lazily.
        4 in Haskell, when using seq to enforce strictness.
        3 in a strict language (like ML).
    

He then goes on to analyse the relationship between point-set topology and
computability, showing that the set of computable functions is in a 1-1
correspondence to the set of continuous functions, under the appropriate
topology:

    
    
        http://blog.sigfpe.com/2008/01/what-does-topology-have-to-do-with.html
        http://blog.sigfpe.com/2008/02/what-is-topology.html
        http://blog.sigfpe.com/2008/03/what-does-topology-have-to-do-with.html
    

Fascinating stuff - but you can see why I decided not to talk about it!

 _Edit:_ Aha, I just realized who you are, and that we've met before..!

------
spacemanaki
Ok, pardon my ignorance, but I'm curious if anyone can comment on how this
does or doesn't relate to the Curry-Howard correspondence ? The very limited
amount of introductory type theory that I've read (mostly Benjamin Pierce's
TAPL and Software Foundations) seems to treat Curry-Howard as a deep and
fundamental cornerstone, but the only places I've seen the algebraic
properties of types explored is in blog posts
(<http://blog.lab49.com/archives/3011>).

Is there some connection I just haven't figured out yet due to mathematical
immaturity? I'm struggling to see the relationship between types as sums,
products and exponents, and types as conjunction, disjunction and implication.

~~~
tel
The answer you're looking for is in category theory. For a long time people ad
hoc recognized that "products" and "sums" exist in many different domains, but
there wasn't a universal method to construct them.

Category theory does just that by treating all of these things as special
kinds of categories and reading off universal properties which link rings,
algebraic data types, logic, sets, topological spaces, etc. etc.

------
pilgrim689
I'm a beginner Haskeller and I'm really enjoying this blog so far. After
reading through a few tutorials, it's refreshing to get some theoretical
background to it all. It helps make the language more intuitive, even if it's
not teaching concrete uses of the theory. Subscribed!

~~~
MBlume
Thank you for causing me to notice that the last two haskell posts I really
enjoyed were on the same blog. Also subscribed!

------
guygurari
So the algebra of data types, as described, is simply the algebra of natural
numbers. I guess this can't be the whole story because it does not describe
types which can get an infinite number of values.

But still I wonder, what is the practical advantage of using this simple
mathematical structure? What does it buy us? Not trying to be snarky, I just
don't see the point.

~~~
tikhonj
A fun example is zippers. A zipper over some structure is a purely functional
way to keep track of your "position" inside the structure, which ideally lets
you move around it easily.

Normally, when we use lists, we do something to the head and recurse on the
tail. This allows us to easily move forwards through the list, but what if we
sometimes want to go _backwards_? It's very easy to come up with a structure
that lets us do this: we just keep track of the previous elements of the list
as well as the remaining ones. This is exactly what a zipper over a list is:

    
    
        zipper [a] <=> ([a], [a])
    

So if we have the list [1,2,3,4], the zipper would let us represent [1,2,^3,4]
where ^ is our position; it would look something like ([2, 1], [3, 4]). We
could then move forwards to ([3,2,1], [4]) or backwards to ([1], [2,3,4]).

This is a very simple and elegant idea, but also very easy to come up with
yourself; no need for any algebra. However, there is an interesting
observation to be made: a zipper corresponds to the _derivative_ of a type! I
think crntaylor is going to cover the mechanics of this in a future post.

This gives us a couple of useful insights. For one, it means we can apply the
derivative operation repeatedly to get as many "cursors" into our type as we
want. We could have a zipper with two positions in the list, or three
positions in the list, or whatever. Additionally, it also tells us how to
generalize zippers to other types like trees. At least to me, this is not
obvious and the algebra really helps here.

So we can go from the very simple structure of using ([a], [a]) to represent a
position in [a] to having an arbitrary number of positions in arbitrary types
like trees. I think that's pretty cool.

Another fun example is packing seven binary trees into one, using complex
numbers. This is a bit more involved, so I'll let much better writers than me
cover it: there's the original paper[1] as well as a nice blog post[2] about
it. I think it's a very cool demonstration of how this sort of algebraic
reasoning can be useful even with a fairly trivial algebraic structure.

[1]: <http://www.cs.toronto.edu/~yuvalf/7trees.pdf>

[2]: [http://blog.sigfpe.com/2007/09/arboreal-isomorphisms-from-
nu...](http://blog.sigfpe.com/2007/09/arboreal-isomorphisms-from-nuclear.html)

Also, recognizing this sort of structure can help us organize our programs.
Often, even trivial abstractions are useful just for organizing code even if
you don't get any especial insight from them. For example, monoids are very
trivial, but are extremely useful for writing libraries.

Also, I think this is exactly the sort of abstraction which would make writing
generic programs much easier, but I do not know enough about generic
programming to be sure. (Here it's "generic" in the Haskell sense--programs
that operate based on a type's structure and apply to many different types
rather than generic in the Java sense, which is just parametric polymorphism.)
Just something else to look into if you're curious.

~~~
Tyr42
Do you know of any more of this cool stuff? I've already looked at 'Seven
Trees In One' and zippers and want to go further. I'm just learning
combinatorics and classes and want to relate it to programming structure.

~~~
emillon
You can read Huet's seminal paper on zippers [1]. I found that Edward Z Yang's
article[2] explains well the more general zippers.

The generic operation of differentiation to create the the type of zipper is
explained in this article[3] from Conor McBride.

Another fun fact about zippers is that they have a comonadic structure, ie
from a zipper you can build a zipper of zippers that reifies the concept of
moving the cursor. That comonadic structure can express "locality of rules" in
cellular automata. This classic article[4] by Dan Piponi uses a 1D automaton.
As an exercise, I described an implementation of Conway's game of life in a
comonadic manner in [5].

[1]: [http://www.st.cs.uni-
saarland.de/edu/seminare/2005/advanced-...](http://www.st.cs.uni-
saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf)

[2]: [http://blog.ezyang.com/2010/04/you-could-have-invented-
zippe...](http://blog.ezyang.com/2010/04/you-could-have-invented-zippers/)

[3]:
[http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.22.8...](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.22.8611)

[4]: [http://blog.sigfpe.com/2006/12/evaluating-cellular-
automata-...](http://blog.sigfpe.com/2006/12/evaluating-cellular-automata-
is.html)

[5]: <http://blog.emillon.org/posts/2012-10-18-comonadic-life.html>

------
rian
awesome post! one suggestion:

following your explanation it seems like the type constructors are more
responsible for determining whether it is sum or product type instead of the
data constructors. this tripped me up when i was trying to figure out why
function types didn't have a size of a * b, and instead b^a. then i remembered
that Add takes the same amount of arguments as Mul in the type constructor
despite it being a sum type. i would hint more that what determines the size
of a type isn't the type constructor but more the different data constructors,
e.g.:

    
    
      data T a b = Foo a | Bar a b | Baz b | Qux (a -> b) <=> T = a + a*b + b + b^a
    

i'm already anticipating your recursive type post :)

    
    
      data Rec a b = One a | Two a b (Rec a b) | Three b <=> T = a + a * b * T + b
      T - T * a * b = a + b
      T * (1 - a * b) = a + b
      T = (a + b) / (1 - a * b)
    

... in haskell's ADT's how do you get the inverses of a type? then we could
solve for the size of the recursive type Rec a b.

------
opminion
Ok, so type == cardinality, roughly speaking, is my understanding of the
article when read with the pure mathematics hat on.

Now that's interesting. Cardinality was the first topic in my Calculus course,
and rather fascinating as a mind opener when it got to infinites.

------
elliotlai
mind blowing, this is just beautiful :)

