SLOBIL is a programming language. You’ve never heard of it because it has a shit marketing department (me). I wrote the initial version on bus 699 to DC in 2018–2019.
Why did you write it? What problem does it solve? Give me the elevator pitch.
I wrote it because I wanted to learn something. It solved my boredom. I never intended it to be useful to anyone.
There are so many programming languages, and they are fine. There is no good reason to make another one. Thankfully, there are plenty of attractive bad reasons. For example, making useless things is great fun and ennobles the soul… Curiosity and forty minutes of free time every day are good enough excuses.
I still use the language daily and add to it whenever it needs to do something else, or I just want to learn how to implement something. It has concurrency, even though I've never had a use case for it. It was just fun to figure out a nice way to do it (based on Ada, which I was playing around with at the time). The roadmap is whatever I want to read about and learn how to do.
This post tells the story of my now almost nine years of working on and hacking with SLOBIL, a programming language you’ve never heard of and should probably never use… I need to learn how to sell better.
Like all good stories, this one has a moral? Maybe.
I wanted a language that had two features:
The language should be easy to use interactively, i.e., the REPL should be convenient enough that a “program” in the language could just be a set of functions you load into the environment.
I had this vague idea of making a “dictionary” the core data type. This eventually transformed into the prototype-based object-oriented system that SLOBIL is centered around, but that wasn’t there at the beginning.
Given these two requirements, I didn't start planning and fleshing out the language—that’s work, and I was distinctly not about that for this project. I just started writing code to see what I would come up with.
Code is a much better language for designing a program than English, anyway.
These two requirements implied the language should have very limited “syntax.” It should have very few special forms, and it should look “command-ish” to be easy to use interactively. I imagined it would end up looking visually Tcl-ish, but with LISP-y semantics. So, I came up with this as the basic structure.
OPERATION DATA DATA DATA ... DATA .Where the OPERATION is some built-in operation of the language, and DATA are various values. The idea is that this is basically like a normal shell command, except it ends in a period because I hate newline-ending code.
The first step was to write a few basic data types like INTEGER (initially just ints, eventually bigints from GMP) and REAL (just C double), and add some basic arithmetic operations.
Now, I could write things like:
add 1 2 3 .
-> ans = 6I played around with my over-engineered calculator and realized the next problem: how do I type in 5 x (2 + 3)? Okay, so of course I could do:
add 2 3 .
-> ans = 5
mul 5 ans .
-> ans = 25But that seemed cumbersome. So, I added the first piece of syntax. A way to group substatements. I decided to use square brackets like this:
mul 5 [ add 2 3 . ] .
-> ans = 25The square brackets work by executing all the statements inside them (you can put as many statements as you want), and then the argument equals whatever is currently in the ans variable after those operations finish. The arithmetic operations set the ans variable to their result, so this worked naturally.
Next, I started thinking about how to assign values to variables. The first thing I realized was that if I wanted to keep the regular syntax, then variable locations needed to be first-class data types. So I created “Slots”, a data type that refers to the location of a variable.
Slots aren't limited to particular types. Any type of data can be put anywhere. I needed this to be true given the “ans” solution above, because “ans” is a particular Slot, and it needs to hold any data type because all operations write their return value to it. So the language became strongly typed and dynamically typed. I didn’t initially have an opinion about which direction to go on typing, but the decisions along the way converged on that as the solution.
A major plus to using Slots is that the data locations are data themselves, which enables neat tricks. It lets us transform and manipulate data locations instead of the data itself, which can improve performance and simplify code.
Another advantage of Slots is that we don’t need a special form for assignment. I added a set operation to take care of assignment:
set /x 2 .
print x .
-> 2The key idea of “slots” is that you refer to the location, the slot itself, by preceding it with a forward slash (the actual symbol went through several iterations, including $ for a while, but I’m skipping to what the symbol ended up as). You access the data stored in that slot by just omitting the forward slash.
For example,
set /x /y .
print x .
-> /y
set x 3 .
print y .
-> 3Notice how the second set doesn’t have a forward slash in front of the x. That means we access the actual value stored at Slot x, which turns out to be Slot y's value. So the set operation assigns Slot y to 3.
Now that I had a concept of data location, I reworked the native operations from hard-coded lookups into first-class data types, inserting them into the required slots.
That's when the language’s structure started to come together. I realized I was really creating a dictionary of data stored at Slots. Clearly, it would be useful if this “table” was its own data type. Eventually, I would call these objects… Objects. Because I’d written Objects initially as just the global scope that contained the mapping between Slots and Values, Objects function as both scopes and data in the language. So, you can pass scopes around to higher-order functions that run code inside certain contexts, etc.
set /env [ object /x “Hello” /y “Goodbye” . ] .
print x .
-> Error: No data found at /x.
in env ( print x . ) .
-> HelloAt this point the language had developed a personality, a recognizable way of doing things that just sort of emerged as I kept solving problems incrementally.
I don't think it’s a bad way to start a new project. Start with the project’s core premise and key constraints, then keep solving problems to get to the next step. Solutions that solve problems while respecting constraints survive. The bad ideas die off. Creation by evolution, not design.
It reminds me of the way you prove things in mathematics. You start with a few premises and try to figure out how to get somewhere useful. In the end, we write it up as Theorems and Proofs, as if we knew what we were trying to prove from the start, but that isn’t how you work on it.
At this point, the language still didn’t have that key element of structured programming: functions. So I had to add another data type.
I thought it would be interesting not to attach an environment to functions. Instead, the data type (“Instruction”) would just represent literal code that could be executed with arbitrary Objects giving the context. They know nothing about the environment in which they are defined.
set /add-one ( add t 1 . ) .The key thing you can do with instructions is call them. Because they are just code, they don’t have an environment, so you have to provide one, like this:
call add-one /t 4 .
-> ans = 5This calls the instrument inside an environment where the slot /t is set to 4.
Of course, it would be annoying to have to type “call” a lot, since calling Instructions is something any well-structured program will do a lot. So, I added a little sugar to the syntax. I expanded the normal OPERATION DATA DATA form I’d been working with. Now, the first item in a statement did not need to be an Operation. It can be some other data type. When it is another data type, a particular default operation is inserted implicitly as the first element of the statement. So we can also just do:
add-one /t 4 .
-> ans = 5This led me to think that this kind of syntactical trick would be useful for other data types as well. For example, set would be a common operation, so why not just assume it if a statement starts with a slot, i.e.
/x 2 .
print x .
-> 2I realized I needed a third kind of parenthesis. I had one for substatements (immediately executed code) and instructions (code as data), but I realized I needed another type of substatement to write conditional statements naturally: lazy substatements. So, I got the new rule that anything between {} would only be executed whenever the value was actually required by the operation. This enabled us to write if statements because now:
if True { print “Hello” . } { print “Goodbye” . } .Prints Hello, not Goodbye, because the third element of the statement is accessed only if the second is True, and the fourth only if the second is False.
By far the most-used SLOBIL program of all time is the time-keeping program I wrote to track hours for consulting work. This program led to several syntax conveniences being introduced to the language as I got more day-to-day experience with the language’s quirks.
It looks like this:
You can type:
client .And get a menu of current clients to select from, then you type:
start .At the end of the day, you just type:
report 2026 5 3 .To give you a report of how many hours you spent working for each client on 2026–05–03.
I’m writing about SLOBIL in this here blog, even though it has nothing to do with Statistics and Economics, because it’s one of the few projects I’ve worked on for almost a decade. Most of what I do is ephemeral. An analysis for so and so to decide if we want to do such and such, which dies as soon as the decision is made, like a male spider post-coitus, its purpose fulfilled… okay, I need to work on less disgusting similes. Maybe a little model that lasts a few years until it no longer applies to the biz.
But I’ve worked on this useless, silly little programming language longer than I’ve been married. Every now and then, when I think of a little thing to fix or improve, I boot up Emacs. It’s a nice way to pass the day. I wrote a bit of it before the rehearsal dinner.
I started writing it well before the LLMs blew up. I still haven’t set the robots on it. They’d undoubtedly improve it.
But it’s not meant to be improved. As I’ve been saying, SLOBIL is useless, fit only for being a fun thing to tinker with, when the mood strikes.
The Robots make programming feel like work, like you’re trying to “accomplish” something. You can’t tinker with Robots. They press you to Accomplish Tasks (tm).
I knew nothing about how to write a programming language or the concepts you’d have to figure out (what is the internal representation of code objects?), but I learned by realizing either the first way I tried was horribly inefficient, or it just didn’t work.
I wrote the parser by hand. Practically, it made sense to leverage flex and bison, but it was fun to figure out an algorithm that worked well.
I kind of wonder if I would’ve bothered to learn all that if I could've started with a skeleton by typing, “write me an interpreter for a language that looks like this…”
I’d like to think I would’ve. It was a good time. But who knows? It’s hard to resist the temptation when you’re tracking down memory leaks.
If you’re curious, the full documentation for SLOBIL is here: https://zflynn.com/slobil/slobil.html, and you can download it from GitHub.
Thanks for reading!
Zach
Connect at: https://linkedin.com/in/zlflynn



