Where am I going here?
Learning other programming languages is useful because is causes you to question, and hopefully expand, the practices you use in the programs write day-to-day. But expansion can be in more than one dimension; Erlang may force me to think about the trade-offs of concurrency and immutability but I am still none-the-wiser about how Erlang achieves its blindingly-fast context switching. I’ve learned the power of Common Lisp’s conditions/restarts but don’t really grok why they’re faster than Java’s exceptions.
There are also some questions no programming language by itself is going to answer; what are the benefits of converting code to CPS during compilation? Everyone seems to be building JIT compilers on LLVM; how much work is that? Could we eliminate NullPointerExceptions by just removing the concept of null and telling programmers to suck it up?
Some things can only be learned by doing. Time to implement a language rather than just learning one.
I didn’t want to get bogged down in language syntax design and parser details (not yet anyway), so I decided that the first run should be to implement a Lisp system. The core of Lisp is simple to parse, flexible enough to implement any concepts that interest me and famously easy to bootstrap. The initial plan was to implement a basic Scheme-like interpreter in Python, implement compilation to LLVM, then re-implement the compiler in my minimal language in the classic compiler bootstrap pattern. I figured the Python/Scheme implementation shouldn’t take me long as I knew Lisp pretty well.
About 200 lines of Python in I realised I didn’t know shit.
Back to basics
One of the insight that learning lisp brings is realising that virtually all programming data-structures and operations can be implemented via lambdas, but I quickly realised that I didn’t actually know how to do much of this in practice. Oh well, back to the books; SICP and in particular Lisp in Small Pieces.
Having revised the fundamentals, I initially went on to implement a metacircular interpreter in Chicken Scheme. However this rapidly became confusing; you have to constantly keep track of which version of lisp you’re currently working with; the implementer or the implementee. And if your implementation of lambda is just a call to the implementation language’s lambda then how much have you really learned? So once I had something equivalent to LiSP’s basic evaluator I ported the resulting code to Python, building up from cons cells and the fundamental first-class objects (functions, integers, strings and symbols).
Where I’m at
At this point I have a core Lisp-1 interpreter with a handful of primitives defined. The next step is to implement CL-style macros, and then start looking at compiling this down to LLVM. Once I have a working executable I will then re-implement the compiler in the new language. This then will become the test-bed for adding whatever aspects interest me; immutability, monads, unnullable references, etc.
I’m not planning on rushing this; this is not intended to be a production language but a vehicle for learning. If I need to wander off on a tangent of digging into Oz or OCaml for inspiration then I will. But even at this early stage I’ve learned a lot about the theory and practice of language implementation and the limits of my own (assumed) knowledge.