With Crafting Interpreters, I felt like there was a reasonably small self-contained language I could come up with that covered almost all of the concepts I wanted to teach: variable scope, functions, closures, classes and dynamic dispatch, control flow, etc.
With type systems, there are a lot of forks in the design space and no clear "best" path:
* Does the language have subtyping or not?
* Are generics erased or reified?
* Is generic code specialized at compile time or not?
* Is type inference local or Hindley-Milner?
Any choice I make here would miss out on a lot of important material from the unchosen branch and disappoint readers who wanted me to take the other path.
Maybe the right answer is a broader survey-style book that tries to cover a bunch of different options (like Types and Programming Languages). But then you lose the fun of building one coherent thing.
I will not fault you if you don't get to any such project (I remember discovering Crafting Interpreters around the time you were like... halfway through part one? and seeing how long it took you to get through the rest of the book. The care you take to both give useful technical information and make your writing clear takes a great deal of work), but I would certainly read it as I've read much of CI if it ever exists (I need to finish going through part 2, I'm up to Closures).
There are (inferior) versions of the same in C and Java. I'd use them together with the ML version.
For me, when I learned it myself, I simply took the toy implementation at https://github.com/wh5a/Algorithm-W-Step-By-Step/blob/master... realized that it was written in an extremely outdated style, modernized it, cleaned up, and ended up with https://gist.github.com/kccqzy/fa8a8ae12a198b41c6339e8a5c459... Then I proceeded to change various things to "break" the algorithm and see how they are broken.
I’m always happy to help anyone going down this path: zellyn@(most things) if anyone reading this wants to try but is feeling nervous.
I opted to translate into Go as I went, which meant:
- a lot more work/time
- slower progress
- no “if you fill in this missing piece, we’ve provided the rest so your compiler will work end-to-end”
- an annoying amount of trying to print ASTs exactly like the C++/Java code for validation purposes
But I got to use Go instead of those other languages! ;-)