In particular, I'm curious why you dropped multimethods in favor of classes in Wren.
EDIT: I see you've already answered this question (http://wren.io/qa.html#what-about-your-other-languages)
1. Julia came out. It looks to me like a really nice language that is heavily based on multimethods, and is orders of magnitude more mature than Magpie. In many ways Julia fills the niche I was aiming for with Magpie and probably better than I could have.
2. I hit a dead end in the VM implementation. This is an amateur mistake on my part, and obvious in retrospect, but it turns out writing a VM in C++ with virtual methods and also using a moving GC is a Bad Thing. It means in the middle of a method, "this" could disappear out from under you. Once I realized that, I realized I'd need to significantly rework the entire VM to fix it. Oops.
3. I'm not a sophisticated enough VM hacker to implement multimethods efficiently. I might get there eventually, but getting Magpie to a state where it's fast enough to be usable for real programs is very likely beyond my skills, especially with my limited free time.
Wren was designed deliberately to be a language that I personally have the ability to design, implement, and ship. There's definitely something to be said for being more ambitious and trying to push one's own envelope, but I wanted Wren to be a think that I could make and get to something usable with a realistic amount of effort.
> it's been somewhat interesting to not have to deal with the expression problem.
Yeah, I think it would be really fun to hack on a compiler in a language with multimethods.
There's very different approaches taken by production grade VM's today, ie the difference between JVM, MRI and CPython. Will there be any introspection into the theory vs implementations in production today.
I've found books like "Ruby under a Microscope" to be invaluable when driving into a completely new runtime. Was hoping this would be a good spot to collect many VM approaches.
1. BYOL is done and my book isn't (yet). :)
2. BYOL aims to both teach you how to program in C and how to implement a language at the same time. Mine assumes you are passably comfortable in Java and C and focuses on the language stuff.
3. Lisp is, of course, a very different language from the imperative OOP language my book uses. BYOL teaches you about s-exprs and some macro-related stuff. Mine covers a more complex grammar, assignment, classes, etc.
4. BYOL uses a provided parser combinator library for building the parser. Mine has you build a lexer and recursive descent parser from scratch and goes into greater detail (though not as much as in a PL textbook) about grammars and the theory behind it.
5. If I recall, BYOL avoids needing a GC by passing everything by value, including deep copying trees. That's an interesting way to sidestep the problem but is an uncommon choice for a language. In mine, we first rely on the Java GC to do our dirty work. In the C interpreter, we'll build a precise mark-sweep GC from scratch.
6. My book, for better or worse, is going to end up being quite a bit larger than BYOL. You can probably work through his over a long weekend, where mine is more of a holiday vacation kind of thing.
---
Other pet peeve of mine is how build control-flows. Using continuations is complicated (I'm using F#) and cause a drop in performance. I wonder what else could be use that allow to make exceptions/coroutines.
Or how make delimited continuations to work reasonable well. The code I have found is in scheme/lisp and it look hard to dechiper.
No, the book will only go into implementing single dispatch. I'd love to talk about implementing multimethods, but I don't know how to implement them very well either. :) I've read a few papers on it, but there doesn't seem to be a lot of literature. Maybe it's mostly locked up in tribal knowledge and you have to be in the right room with the right people to learn it.
This is sad.
OT: With Google Chrome's new Turbofan, and its altered inlining budgeting, there now seems a chance of inlining dispatch trees. Combined with PICs, and cpu branch prediction and speculation... there seems hope for wizzy multiple dispatch in javascript. Maybe. I don't know of anyone pushing on it.
Kotlin goes this direction, for example, though it still requires an annotation on all functions that participate in continuations. This makes it fairly straightforward to tell where the `prompt` is- the root `suspend fun` passed to whatever API drives it. Making every function implicitly `suspend` would let you treat any function as a `prompt`.
I've actually been hands deep in the bowels of a widespread production-use interpreter, so I'm hardly new to the topic. Yet as soon as I started reading your book yesterday on the way home, I haven't been able to stop reading. While I have a reasonable grasp about most things covered (so far), I find myself making additional connections and having implicit questions answered that I didn't know I had.
So thank you very, very much for this!
IIRC you say that all non-trivial interpreters have abolished ref-counting and usually using some mix of tracing and ref-counting (o//r just tracing GC). Your example for an interpreter that was using ref-counting is Ruby.
The example of a non-trivial interpreter that continues to be fully ref-counted is perl.
[rant]Worse yet, perl goes fast and loose in one edge case: it eschews ref-count operations on values on argument stacks, which is a source of bugs that couldn't be more hilarious.
So yeah, perl doesn't detect cycles. It's really quite sad. It's also not fundamentally impossible to add in a tracing GC for detecting cycles. But it'd be a reasonably daunting task given the code base.
I've occasionally thought about whether it's possible (and if so at what cost) to move perl (5) away from ref-counting. Perl (the language) happens to give strong guarantees about when objects are destroyed / hooks are triggered, and that's commonly relied upon in RAII-like use cases.[/rant]
Any ideas on how to make it run?
fun A (k, x1, x2, x3, x4, x5) { print k; fun B() { k = k - 1; return A(k, B, x1, x2, x3, x4); } if (k <= 0) { return x4() + x5(); } else { return B(); } }
fun F(i) { fun G() { return i; } return G; }
print A(10, F(1), F(-1), F(-1), F(1), F(0)); // expect: -67