Here's the actual law:

> All non-trivial abstractions, to some degree, are leaky.

It itself is somewhat leaky: it never defines "non-trivial"! Perhaps "non-trivial" just means "complex enough to be leaky", rendering the whole thing a tautology.

I've been thinking about different philosophies behind abstraction for a while. My ideas are a little unformed, but I think they're still useful. Basically, as I see it, there are two different ways to define abstractions.

There's the usual way: you come up with a new notion that has additional capabilities. The abstraction adds to the underlying concept. Perhaps it also hides some detail, but the main action is adding. Some of the leaky abstractions from the post really fit this: TCP, for example, adds new capabilities and behavior over the underlying protocol; C++ strings add a bunch of string-specific capabilities over char arrays; NFS adds filesystem capabilities to a network connection.

These abstractions are largely "leaky" because what they add does not always map nicely to what's underneath. They're also interesting because each abstraction only fits one thing--TCP is just for network communication, C++ strings are just text in char arrays and so on. Ultimately, all these abstractions are just more structure over whatever they're based on. Again, the key idea is adding.

The other sort of abstraction is what I think of as "algebraic" or "Haskelly". Instead of adding, these abstractions take away. They expose some feature of the underlying concept and throw everything else away. A monoid, for example, just exposes an associative operation from whatever set (or type) it's defined over. It can't really be "leaky" (at least in the same way) because it's showing what's already there. The normal sort of abstraction is like a structure built on top of a foundation; this sort of abstraction is like a lens or a view into a property of its foundation.

A very important result is that these abstractions are very independent of any basis or use-case. Monoids come up everywhere. Strings form a monoid. Numbers form a monoid (in many different ways, even!). Booleans form a monoid. Certain classes of functions form monoids. Note how I said "form": even if we ignored it, the monoidal structure would still be there. Talking about these things as monoids doesn't add anything but just highlights something already present.

Instead of adding capabilities to the underlying object, this sort of abstraction instead exposes structure shared by otherwise distinct objects. Monoids represent things you can combine; groups represent things that are somehow symmetric; rings represent things that are sort of like numbers; fields represent things that are really quite a bit like numbers; F-algebras represent things that are like any of the other abstractions I've listed (meta-abstraction!); functors represent things that can be transformed while preserving structure and so on.

These abstractions also let us talk about different things uniformly and generically. We can reason about and write code against these abstractions without worrying about any particular underlying object. By doing this, nothing but the abstracted structure can do anything--details of any given thing can't leak through because we have to be generically compatible with every instance of the abstraction.

Functors are a great example of this. If we wrote code using lists, we could do all sorts of things: always return an empty list, add elements to the end, drop elements from the front, change the order... If we wrote a code just using a functor, it would still work for lists but all we'd be able to do is map a function over the elements. Nothing else. Always returning an empty lists wouldn't even be a meaningful construct! Clearly, using this abstraction has taken away our capabilities, but it turns out this is often a good thing.

But this is where "non-trivial" comes to bite us. Are any of the abstractions I listed actually "non-trivial"? I'm not sure I could claim that. To be generally applicable, all of them have as little structure as possible. Ideally, you want to limit your capabilities as much as possible to express whatever you care about. This usually means limiting them a lot. All of the things I listed can be expressed as a handful of functions and a handful of algebraic laws--a few lines of description is enough to understand them completely. They are nowhere close to something like TCP!

Ultimately, I am not even sure it really makes sense to compare the two sorts of abstractions. But they are certainly both approaches to abstracting, albeit in different ways. It's something that's worth thinking about the next time you have to design or use an abstraction yourself.