3 ms·
I've been in the compiler camp originally, simply because I found it more intuitive to translate a language into another one than interpret syntax tree data str
by pwpwp 14y ago
I've been in the compiler camp originally, simply because I found it more intuitive to translate a language into another one than interpret syntax tree data structures.
But recently I've started playing with interpreters and have found them very appealing.
There are multiple reasons for this:
- With an interpreter, there are no separate object files. So you only need to slurp the source code and call eval on it. This reduces workflow overhead to zero and should not be underestimated. For example, in a JS-based language for browsers, this means you don't need an extra set of scripts for a native JS environment (like node.js) for creating JS object files.
- With an interpreter, there's no phase separation. In a compiler you're always dealing with expressions, and you can't deal with the values they evaluate to. In an interpreter you have access to both, so you can do a lot more fun crazy things.
- With an interpreter, the implementor keeps more control of programs. It's simply much easier to instrument and manipulate the execution of programs when you are interpreting them than when they run on their own on the target architecture.
- Potentially smaller code sizes. In languages with macros, source code is highly compressed and irredundant - basically all boilerplate has already been stripped away by the programmer. If you compile this code, you have a huge code expansion compared to the source code.
- xyzzyz 14y agoThe first three reasons you mentioned apply equally well to SBCL, a Common Lisp compiler.
- eru 14y agoOr even to the way cPython handles their byte-code compilation with the pyc-files. (And it would be conceivable for the compiler to do more than what cpython does to get pyc-files, and not change the workflow.)
- groovy2shoes 14y ago> - With an interpreter, there are no separate object files. The article is talking about JIT compilers, not AOT compilers. JIT compilers don't need to generate object files. Compilers can provide eval. JITs use more memory at runtime than interpreters, with AOTs there is no runtime overhead from language processing. > - With an interpreter, there's no phase separation. In a compiler you're always dealing with expressions, and you can't deal with the values they evaluate to. This is just plain wrong. Interpreters can have phase distinctions. Any language that could be interpreted could just as well be compiled. I don't even really understand your argument here. > - With an interpreter, the implementor keeps more control of programs. What does that even mean? > - Potentially smaller code sizes. Interpreters that offer macros will have to expand the source code, too, which will use up memory. The difference is that now macro expansion is happening it runtime, which impacts speed. Furthermore, (dynamically-linked) compiled binaries are almost always smaller than their source code. There are benefits to using interpreters versus compilers of any sort, and vice versa. This article is discussing the drawbacks of JIT compilers in particular.
- gliese1337 14y agoThis is just plain wrong. Interpreters *can* have phase distinctions. Any language that could be interpreted could just as well be compiled. It might be clearer to say "interpreters don't have to have phase separations. And that makes some things easier to do than they are with compiled code. For example, I'm working on a Lisp dialect with first-class macros that are syntactically indistinguishable from function calls. Theoretically, yeah, I'm sure I could compile it; I know compilers have been written that handle first-class macros; but I have only the vaguest idea how. But in an interpreter, it's trivial. > - With an interpreter, the implementor keeps more control of programs. What does that even mean? pwpwp said precisely what it means: "It's simply much easier to instrument and manipulate the execution of programs". I.e., as I understand it, it's way easier to build a highly informative debugger into an interpreter than it is to attach a debugger to compiled code.
- groovy2shoes 14y ago> I know compilers have been written that handle first-class macros; but I have only the vaguest idea how. But in an interpreter, it's trivial. Yeah, compiling first-class macros can be very tricky. I cut them from the compiler for my own Lisp because I didn't see the benefit of having them (beyond them being neat). If you know of some use-cases where first-class macros are useful, I'd love to hear about them. I might change my mind about cutting them. The thing about first-class macros, though, is that the macro expansion phase is still separate from the execution phase. It's just that both phases now happen at run time rather than having expansion at compile time. > I.e., as I understand it, it's way easier to build a highly informative debugger into an interpreter than it is to attach a debugger to compiled code. That makes sense. I couldn't have derived that from the original statement. Thanks.
- gliese1337 14y agoIf you know of some use-cases where first-class macros are useful, I'd love to hear about them. I might change my mind about cutting them. I got nothin' in terms of practical use cases. It's mainly a research thing. It annoys me that Lisp function calls look exactly the same as macro calls, but in fact behave differently, and making macros first-class fixes things up so that same look == same behavior. That way you can do crazy stuff like (map (fn (a) (apply or a)) '((#t #t)(#t #f)(#f #t)(#f #f))) Where normally this would produce an error "or is not a procedure". My interpreter implements both lambdas and macros as syntactic sugar on top of vau expressions, so if I had a good way of compiling vau expressions, then the first-class runtime macros would be automatic. In the interests of efficiency, I'd like some way of doing static analysis to do as much macro expansion as possible at initialization / compile time, but that's dang tricky figuring out which macros will end up bound to which names before runtime.