AM011 – An Imaginary Compiler

A file somewhere feels its bits munged around, as our programmer saves it. “ERROR!”, yells the terminal output again. Eyes roll. What was it this time? They wondered. Did I pass in the wrong variable?

Heads up!

This is a short story exploring technical blogging as narrative non-fiction. It reads like a tale, and while it aims to entertain and be relatable, it attempts to expose a set of underlying technical points.

A file somewhere feels its bits munged around, as our programmer saves it.

“ERROR!”, yells the terminal output again. Eyes roll. What was it this time? They wondered. Did I pass in the wrong variable? Was it the wrong property name?

“Silly me”, mutters the programmer. It was indeed a typo. “‘email’, not ‘emial’”.

You see, this programmer had been learning to code using an old statically typed language, so they were still not used to computers yelling out loud “you’re making mistakes” this fast, this often, and often even showing how to fix the mistakes. But as programmers do, this one was picking up pace. Today a few lines, tomorrow a few modules.

Quickly they learned that these things, “types”, don’t just mix very well unless you’re very explicit about it. That everything needs to have the right name. That sometimes you make modules that deal with a “thing”, and you hide what that thing is from everyone else.

We say “let everyone in on…”, but we really mean that if some code was a person and some other was another person, these people would share some information.

As time passed, our coder grew fond of this imaginary companion that would yell at them on every turn, letting them discover the wondrous world of computing through their fascinating, buggy programs.

First Job

HR sent the email that morning, announcing our programmer they would be joining a new company! Excitement. On a team that used a modern dynamically typed language. Doubt.

“How is it possible”, our protagonist wondered, “that the compiler doesn’t know what things are? Bytes flowing out of control throughout the controls of the program without anyone knowing what was what at any point in time? Madness”

Our programmer, you see, was used to defining what things were upfront. In this new project, there seemed to be no way to do this that was well accepted. In fact, this language seemed to have a split community of users that couldn’t even agree with each other on whether they liked this unstructured, metamorphing data in the first place!

Some, bent to the will of the language, moved bytes in the hopes that they will be what they should, aggressively checking everywhere before using them.

Others insisted in using something gimmick of the language that looked like a type but wasn’t quite the same. It was a trick, played to give you the sense of security.

And our programmer struggled to find their place in this world, without their imaginary companion showing them how it could be better.

Amalgamating

One, and then two tests printed yes. A third test goes red. Now the first one is broken. “Ughhhh why is it so hard to refactor this f*cking thing”, someone complained on the back.

A chair turns around, and rolls over, offering help. It seems that someone changed the underlying structure of one of these objects, and while there was no tests that proved it was broken, production code was failing.

“I mean I get it, we’re not making a class for this, since we are functional programmers or whatever but wouldn’t it be nice if we had a single definition of what this thing looks like”, argued the complainer pointing at a stacktrace. It was ridden with irrelevant details, but if you squinted, you maybe would find that they had tried to access some data that no longer resided inside of the object. “I’d love it we could catch these things earlier on”.

Long ago our coder used to work with a companion that would complain very early on, very often, and very loudly. Long ago they used to write one module per “thing”, whatever that thing was. Sometimes creating a “thing” meant it must have passed certain requirements, so the rest of the code wouldn’t need to check for these things later on. But that was a long time ago.

And our programmer, wondering why not just make a module for this thing, shared:

“I mean, sure, we don’t quite have a compiler complaining at us on every new line we write, but I’ve noticed it does complain about some things

So hear me out, what if we just make a new module with functions to both create, but also access and alter this thing.”

“Ugh more code?”, the complaining continued.

“Yeah so instead of just passing that data around, you would be forced to use this module, and then if we need to change something like a name of one of its properties, then the function names will change and the compiler will complain about that. I mean it can’t tell the property name has changed, but it can tell that the function name has, right?”, our programmer proposed, sketching pseudocode.

module(OurThing) {
  %% fields: A, B, C

  fun create(a, b, c) {
    %% check if they're good
  }

  fun a(thing) { thing[0] }
  fun with_a(thing, newA) { {...thing, 0: newA} }

  fun b(thing) { thing[1] }
  fun with_a(thing, newB) { {...thing, 1: newB} }

  %% etc
}

This made sense to our programmer. What else could be changed from how the code was written in this new language to leverage whatever information the compiler was already giving?

A dismissive voice across the room joined the conversation without a lot of interest. There seems to always be at least one of these. “Pff, that’s just creating your own shitty classes again”.

“Sort of”, rebutted our protagonist, “except then we would control the internal structure, and nobody could accidentally rely on it without like clearly breaking through that barrier of abstraction!

Classes would look like regular objects to everyone else, and they’d invite the coupling.

We could do this for every piece of data we work with, just one tiny module, you know, and it could even do the minimum validation we need to make sure the data is good, which would save other layers of the code from having…to double check…these things…”

Something clicked in our programmers heads.

The rest of the team turned towards the discussion, curious and inquisitive, as the programmer started sketching onto a whiteboard squares and arrows, illustrating the problems they were having, explaining why this kind of abstraction made sense when working with a different language and how it solves the problems they have right now while allowing them to build systems that are more explicitly and intentionally coupled than the accidental spaghetti of data the project seemed to evolve into.

Question and answer flew back and forth and back again as the team brought code onto screens and debated the ugliness, effectiveness, efficiency, readability, maintainability, extensibility, and testability of the code that could be.

And the team agreed to give it a shot.

That was the moment our programmer realized, whatever the language to use at work, whatever the project that needs to be built, all they needed was a tool to complain about the way things were at every turn and show them how things could be fixed, how things could be better. A tool like the one they had learned with.

From now on, they will always have their own imaginary compiler.

Thanks to Malin Männikkö and Fernando Via Canel for taking the time to review earlier drafts of this story.

Subscribe to Abstract Machines

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe