This note is part of a series.
I am writing this while listening to Hustle Bones by Death Grips after completing the steps 2 and 3 of MAL. Since my last post, I've implemented rudimentary error reporting, amongs the required functionality to pass the tests for steps 2 and 3.
I can't say I've learnt anything significant during this process. When it comes to MAL, I already had an idea of how a tree-walking interpreter works and also understood the idea of environments. My implementation of these concepts is admittedly laughable, and I would like to implement a serious Lisp at some point with all the right algorithms and optimizations, but as of right now, I am actively doing the stupid thing so as to get the full picture of it all quickly before getting into the serious stuff. For instance, my Vector data type isn't growable since my arena implementation doesn't have free-lists or other ways to facilitate a realloc
mechanism and I feel like spending time on figuring that out would detract from my initial point of doing MAL—to understand how Lisp interpreters work.
What I am feeling happiest about is how much C programming I've been doing. I feel like I'm really cutting my teeth with it. I've been dealing with the infamous memory bugs and Emacs' gud-gdb
has been my best friend. These memory errors have been mostly off-by-one, or me forgetting to check for a null pointer, but I've gotten faster at spotting them. I can't know if this skill is general. It might be that I am most familiary with my codebase and my coding style instead of memory errors in the domain of the C programming language, but hey, a win is a win.
One thing I'd like to touch on is error handling. In my advent of adding proper error reporting into the REPL, I had to face certain error handling decisions. I've found that the best approach is to follow the principle of using an assert when something should never happen (programmer errors), and returning an error value upstream when a bad thing happens (user errors). This technique isn't in the slightest novel and is fundamentally integrated into languages like Rust. It works really well, but it can get quite tedious in C. The main issue seems to be that C doesn't have generic data types which makes writing a general Result
monad-y typedef hard. I scoured the internet for clever solutions before deciding to write a Result
type per occasion. My rationale? It's simple, and I didn't need more than 2-3 forms of it. My error handling doesn't need to be generic anyway. For each code path, there is a pretty distinct error handling mechanism and luckily the return type is uniform through that path meaning that I only need one result type per code-path, per sé.
That's about it for this post. Nothing more to report besides progress made, and how I haven't hated C yet.