Let’s imagine a language that asks you to bring your own runtime.
To understand what I mean, let’s review a little bit about how programming languages work.
Start with a Calculator 🧮
When you type things into your ti-89 or whatever overpriced 1980s technology schools have students using these days, you’re typing a language. That language has a lexer and a parser and a runtime and everything.
Take the following:
1 + 1
When the calculator encounters 1 + 1
, it goes through and converts the string of characters it receives into tokens, something like this:
INT(1) PLUS INT(1)
Then it runs those tokens through a recursive descent parser that looks something like this:
int() {
return items.pop()
}
expr() {
left = int()
op = items.pop()
right = int()
return int() + int()
}
Finally, it will call expr()
and return that to the user.
Programming languages end with an expression
At the end of the day, a programming language will evaluate its literals and then call some operating system functions via its runtime.
So a more “program-y” language, might do something like this:
print(1 + 1)
Which would transfer into this:
PRINT LEFT_PAREN INT(1) PLUS INT(1) RIGHT_PAREN
Which would turn into something like this:
root() {
system.print(expr())
}
Back to our fantasy language
In existing programming languages, the runtime is hidden far below the compiler. In fact, the whole point of the language was to abstract the runtime away from you.
You don’t need to know what system command executes printing a function or opening a file because you can just run print()
or open()
.
What would happen if we made runtimes a first class citizen?
Instead, let’s imagine a language where you were encouraged, supported, and supposed to write an alternative runtime.
For example, if the programming language says print
, you could define exactly what print
means.
Instead, your print could call arbitrary text-to-speech code. If it was in the web, it could return a component. Or it could just call some arbitrary function.
Change your perspective
Often, we think about software as an ever-changing system that runs on an extremely unchangeable base: the languages runtime. The whole point of most languages is to give you a tool that runs the same on every person's computer.
The runtime abstracts away multiple operating systems and hardware so you can always be sure that the logic stays constant.
But by doing so, we often accidentally coupled the logic of the code to the domain it runs on. Countless hours have been wasted rewriting the same “web server that talks to a database” solution.
Instead, what if instead of working with a constant runtime, we worked with constant code.
What if you just pulled the “web server code” from a package manager and rewrote what lower level pieces did? Rather than rewrite the the whole thing yourself?
Your CRUD app could be rewritten to work on iOS, Android, and web by just rewriting the outputs of the language.
Woah, woah, woah isn’t this super insecure?
Probably. Though it could also be a security boon. Imagine being able to give the power of the runtime to an individual user.
Anyone who ran the program could no-op unsafe calls. No more chmod
or network calls for untrusted programs.
What’s the point of this?
Building something like this would be dumb. Most of the problems that could be solved with a language like this have been solved in other ways.
Operating systems have better access controls, modularity lets us have our own pseudo-runtime, and this may be too odd to be useful.
But exploring alternative ways to solve existing problems can work interesting muscles in your brain. It’s also a little fun.
Top comments (9)
I'll point out that swapping out the runtime is not that crazy an idea and is something we do every day when we load the same JavaScript application in different browsers or when you run the same idempotent code with node or a browser. Or when you move your .net framework application to .net core or pcl. Or when you move between Java implementations.
Granted, none of these make it easy to rewrite the runtime, but it's not like we are entirely without a model for how that might work and what it might enable.
Another place to look for inspiration would be the glorious world of lisp macros (Wich are coming to js via Babel macros are they not? And then there is sweetjs as well... Sigh). Swapping out macros is probably a lot closer to what you are visualizing than constantly rewriting the same hardware calls. Here you could do some interesting stuff.
As for writing crud applications over and over, isn't that a solved problem? For a crud application use a cms or a site generator like rails... Something that does all that for you. The issue is that many applications aren't just crud...
This made me think of metaprogramming in groovy. You could write a library in groovy, with the expectation that users of the library will then update the metaclasses to make method dispatching do whatever they want it to. I'm having trouble thinking of a reason you'd want to do things that way, but it could be done.
I imagine this is true for some other languages that allow for metaprogramming as well. I use groovy as the example because it's the only one where I've actually used metaprogramming personally.
Definitely fun to think about things in new ways!
This sounds a lot like moving from technology first (layered) design that typically address the same (usually incorrect) challenges repeatedly ('lets build a web app that...', 'lets build a mobile app that...'), towards domain driven design that first identifies the customer domains where solving challenges add value, and separates those using appropriate isolation from the supporting technologies (like web/mobile/batch, persistence...), so we can deliver the same customer domain solution in multiple ways.
In my mind it's applying DRY at the architecture stage.
This makes me think about lisp and its macro system and more generally about languages where one can change by program a piece of code before it gets executed so one can "choose the runtime" to use, or more reasonably, choose the piece of runtime to hit by the code.
Like Clojure for the JVM, Clojure for .net, ClojureScript, Clojure on Erlang?
Does Clojure really do this? How??
I would guess in the same way you can run a Python program on the “standard” Python VM (implemented in C) or with Jython’s (Java VM), or with Iron Python’s (.Net VM).
"Write once, run anywhere” was a main selling point on the first versions of Java.
It’s the same idea if you manage to think that there isn’t a single environment containing your program, but a chain of them: the program runs in the runtime that runs in the VM that runs in the OS that runs in the actual machine in the end.
We are in a pretty good spot in terms of adding (I.eg. A browser) or replacing (Windows, Linux, Wac) links on the chain.
..eh, java and jvm-like languages exploring this for last two decades or something.
So, code generation targets? Although that often involves having the language runtime run within the target platform's runtime.