Radiant Log #002
Language design is a fascinating topic for programmers. As our principal means of expression, we spend most of our time in a programming language. More than a way to formalize ideas, languages are tools that help us think and through which we grow as engineers.
My journey through programming languages has shaped my understanding of computing; this is why it’s always so refreshing to pick up a new language. Different languages can teach you different things: C taught me computers and memory; Haskell taught me how to think about effects and pure functions; Rust taught me how to write efficient code; Go taught me concurrency; Ruby taught me how to compose abstractions and Erlang taught me how to think about distributed systems. Each language illuminated a different perspective on computers, and a new way to relate with them.
When thinking about language design for the Radiant system, I’ve found it useful to look at programming languages as they exist on what I call the intent/instruct spectrum. This spectrum represents a fundamental tension between two approaches we have to communicating with computers.
The first approach is adopted by languages that try to capture the programmer’s intent, and translate that into instructions for the machine to execute. The second approach on the other hand is used by languages that give the programmer tools to directly instruct the computer how to perform a certain task, bypassing any requirement to specify the intent.
There are many ways to look at this spectrum, as illustrated by the table below, but I find the intent/instruct formulation interesting because it is more about our psychology than the tool.
intent | ↔ | instruct |
---|---|---|
functional | ↔ | procedural |
declarative | ↔ | imperative |
human | ↔ | machine |
high-level | ↔ | low-level |
“what” | ↔ | “how” |
Early in my career I was very much interested in the idea that I could express things in a human-friendly way, and the compiler would do the work of translating that to machine code for me. This was exemplified when I started using functional languages and in particular Haskell, which had a very high quality compiler, GHC that translated very high level expressions into optimized code. This allowed me to write programs in a way that looked nothing like the instructions that would eventually be executed by the machine. This was liberating for a while, it meant that many of the hard decisions (the how) were made for me by the compiler. I didn’t have to think about optimizing memory, performance, algorithmic complexity, etc. as these would be handled by the language and its runtime. In a sense, it felt like this was what the future would look like: smarter and bigger compilers that did more work for you and made your life as a programmer easier.
After Haskell, I moved to Rust, which attempted to strike a balance between the two ends of the intent/instruct spectrum. Rust is I believe the most versatile language in use today, partly due to its ability to play both sides.
With time though, I learned to appreciate the way computers actually work. Partly because software complexity drew me towards lower levels of the stack, and partly because the deeper I delve into certain problem domains, the more I needed control over all the different pieces to achieve my goal. I often return to this quote by Eskil Steenberg, referring to programming in C:
In the beginning you always want results.
In the end all you want is control.
This is what it ultimately comes down to; and in a very real sense, the Radiant platform is an embodiment of this desire for transparency and control over not just a process, but the whole system.