Skip to main content

All You Need Is NAND

·1039 words

This blog post will yet again be one of my messy brain thingies, so I apologize in advance if it is hard to follow. I will try my best to serialize my thoughts into words in such a way that your brain reconstructs the ideas as closely to how my brain does as possible.

All you need is red, green, and blue
#

It is probably common knowledge nowadays, but color is a lie. Not in general, but the thing that you’re probably reading this on - I’m guessing an LCD-like display - doesn’t actually display individual colors. Rather, it decomposes the picture into pixels and each pixel is a mix or three distinct colors: red, green, and blue.

Turns out that if you mix these three colors in different intensities, you can get very close to displaying essentially any color you want. I won’t get to the details of how does this work on the physics level - combining wavelengths and stuff - but you get the idea.

It just works and all you need is the three primitive colors.

Lossless compression
#

There are many algorithms to squash bytes into less bytes. Again for the sake of you not getting completely bored while reading this, I won’t delve into them, but I’ll give you the general idea of lossless compression.

Imagine that you have some data … any data really … computers store them as an ordered sequence of bits that we further chunk up into bytes. Image, video, pdf - all of them are just that. For our example, let’s imagine this chunk of raw data:

11110010 11001000 01000000 11001000 01010000 11110010 01010000

(Spaces separate bytes here; in the memory there’s no spaces, duh)

How would you compress it?

In general all lossless compression does one thing: figure out some way to store just enough information to be able to fully reconstruct this sequence of bits.

A way to make such an algorithm is to ask simply: “What is redundant information?”

Look at the raw data, do you see some redundant information?

If you’re got a sharp eye, you may have noticed that some of the bytes are repeated.

Do you see a way to remove the redundant information?

We could, for example, store the unique bytes once and then just use some short tags that tell us what the actual byte in that place is supposed to be.

If we figure out some nice way to serialize it - for example storing the LUT at the beginning of the file and then the sequence of just tags, we just compressed the string of 7*8 = 56 bits into a sequence of 3*8 + 7*2 = 38 bits. Just like that!

And the cool thing is that we didn’t loose any information while doing that, we just destructed the data into primitives; removed the redundancy.

All you need is NAND
#

This thing left a big mark in my head back in uni. Boolean algebra gives mathematicians a set of tools they can use to compose expressions. It just so happens, supported by a ton of math, that out of all the operators you only need on - the NAND.

You can define any other boolean operator with just using NANDs. This creates some form of completeness that I do no know how to call.

And the cool thing? You can literally build an entire computer just out of NAND gates.

Alright, enough exposition … let’s get to the point.

The NAND of API design
#

On the examples I wanted to show that sometimes we tend to be quite wasteful. You don’t technically need all color LEDs on a display to show almost any color you want. You don’t need the exact bits in the exact order to store them. All you need is NAND.

I think we sometimes get the same problems when designing APIs. But honestly? It’s because API design is very hard. It’s very difficult to not engineer around specific use-cases.

When we’re designing APIs, we’re essentially designing a set of tools that we then give to the user to use, but that becomes very difficult if you cannot possibly think of all the different use-cases the user might have.

So essentially, the hard problem is: How do you design for the unknown?

The way I see it, there should be some way that you can take your API design and prove that it can be composed in such a way that any arbitrary use-case can be completed just using that specific API.

Maybe it’s good to think about the system always being a point in an N-dimensional space of the possible states and you want an API (which in this case defines the set of transitions in that space) that allows you to get to any point in that space - but that might be just me pattern matching unrelated stuff; I’ve no clue if that’s a useful way to think about it.

There have been some specific effort on trying to reduce the mental load on the poor engineer tasked with such a thing - like CRUD - but I’ve yet to see something that we can use to in some way formally prove the “completeness” of the API.

I will leave this blog post open ended on purpose, because this is not a finished thought. I want to dive deep into this topic. For now, I will just describe my set of surface-level hard-to-grasp criteria for an API to be “complete”:

  1. The API has to be minimal - any function it exposes cannot be expressed by combining other functions
  2. All use-cases have to be reachable - if the systems allows such functionality, it has to be reachable via the API (bad example: API exposes only updateEuropeanNationality() that means that non-European users cannot have their nationality updates; ofc unless this is what you want to specifically disallow by not exposing it)

So far I’ve come up with only these. As I give this more thought, maybe I’ll add more. I hope I sparkled a big of brain mess in your heads too. If you have any ideas about this topic, please feel free to reach out; I’d love to chat!