Associate Teaching Professor
Carnegie Mellon University
Esoteric programming languages were a big part of learning to code for me.
These are creative, often minimalist programming languages that push the boundaries of what a programming language even is. Could you design a language that only has 5 commands? Or is only made up of whitespace? Or where every program must be a valid image file too? It is a puzzle both to design the language and to use the language.
Here is a Hello World in one such language:
++++++++[>++++ [>++>+++>+++>+<<<<-] >+>+>->>+[<]<-]>>.>---. +++++++..+++.>>.<-.<.+++. ------.--------. >>+.>++.
As you can see, esoteric languages are... well, esoteric. Too esoteric.
Not only are they hard to use, but they are generally impossible to do anything remotely useful. Of course, these limitations are intentional. For example, you can't build a GUI or build a web server with just Brainf*ck.
I wanted to make an esoteric language that seems like you could do something useful with it. Though you probably won't. But you could, maybe, if you tried just a little harder!
Enter, Hofstadter!
The language supports concurrency, regular expressions, HTTP requests, and file I/O!
?0 https://rosettacode.org/wiki/Hello_world/Text @0 @1 "Hello world" #
Above is a slightly verbose Hello World program written in Hofstadter. You can't set values using literals, so it does an HTTP GET on a website, uses a regex to extract the first instance of "Hello world", and then prints it.
The lines of code are important. Each line is executed concurrently in round-robin style (one command per line starting with the first line) and can store one value at a time (a string). The stored value can be referenced from other lines using the line number. Lines will loop, forever.
Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.
I named it after Hofstadter's Law because if you add another command to a line or add an entirely new line, it will often have consequences for all the other lines. Each line must account for the other lines.
There are only 8 commands, though some of their meanings change based on whether the line's current value is empty. They are:
Action | Example code | Description |
---|---|---|
HTTP request | http://www.austinhenley.com | If the line's data is empty, performs a HTTP GET at the specified URL and stores the result in data. If the line's data is not empty, performs a HTTP POST at the specified URL with the line's data as the request's body and stores the response in data. |
Regex | "a(bc)*" | Runs the specified regex that is enclosed in double quotes on the line's data and stores the first match back in the line's data. |
File IO | foo.txt | If the line's data is empty, reads the specified file's contents to the line's data. If the line's data is not empty, writes the line's data to the specified file. Can be a relative or absolute path. |
Console IO | # | If the line's data is empty, reads from stdin into the line's data. If data is not empty, write to stdout. |
Conditional | ?5 | If the line's data is equal to the specified line's data, continue. Else, restart the execution of this line from the start but keep the data. |
Conditional | !5 | If the line's data is not equal to the specified line's data, continue. Else, restart the execution of this line from the start but keep the data. |
Swap data | @5 | Swaps the line's data with the specified line's data. |
Concatenate | +5 | Concatenates the line's data with the specified line's data and stores it in the line's data. |
Commands must be space separated. Lines are 1-indexed. If you need more than one value per line, you can swap with nonexistent positive-numbered lines as a form of storage. If you need to clear a line's value, you can swap with line 0 which will always be the empty string. If you need a no-op, you can swap with the current line. If you need literal values, you'll need to load them from a file or a website.
The source code can be found on the GitHub repo. There are only a few examples in the repo so please submit a PR if you come up with one!
How did I come up with the language? I tried to deliberately think of features I had not seen in other esoteric languages, which led me to web requests and string processing. Those would make it appear useful. I then tried to figure out how to apply some sort of forced concurrency and messaging passing to make it tricky. I vaguely recall a language that used a metaphor of numbered mailboxes to pass values around, so I twisted that to be implicit, where each line has a mailbox. Then I added a standard conditional. Instead of adding a loop command, I made each line loop automatically! I conveniently forgot support for literal values, and I quite like it that way.
Did I achieve my goal of making an esoteric language that is capable of making something useful? Eh, maybe. I'm quite proud of Hofstadter. It can interact with the outside world, it has some uniqueness, and it is still surprisingly tricky to use. However, I've yet to make any notable programs using it.
I'd love to see what you can come up with!