
Why interpreters matter (at least for high level programming languages)   - vgnet
http://www.ics.uci.edu/~sbruntha/why-interpreters-matter.html
======
pwpwp
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.

~~~
groovy2shoes
> \- 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

        This 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
> 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

        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.
    

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.

~~~
groovy2shoes
Yeah, Guile has first-class macros, but I've never been able to come up with a
practical use for them, either. I still find them neat.

Is your vau work available publicly? I find this stuff very interesting.

~~~
gliese1337
Yup. I've got it up on github at <https://github.com/gliese1337/schrodinger-
lisp>, with explanations blogged at
[http://gliese1337.blogspot.com/2012/04/schrodingers-
equation...](http://gliese1337.blogspot.com/2012/04/schrodingers-equation-of-
software.html) (first installment; there are later posts with the same tags
describing incrementally added features).

The syntactic sugar for macros isn't pushed to the public repository yet; not
till I get a good blog post written up about it. But it's basically parallel
to the operation of "wrap" in Kernel- where "wrap" creates a lambda by
evaluating the arguments to a vau expression, "macro" evaluates the return
value of a vau expression.

I'm doing most the research stuff on the Schrodinger repository, and things
that I like will end up eventually on <https://github.com/gliese1337/vernal-
lang> ; I've stopped actively updating that repository for now because I'm
contemplating re-implementing the "real" Vernal interpreter (as opposed to the
"research-only" Schrodinger interpreter) in Go, so that there's only 1 level
of interpretation going on instead of 2.

------
btilly
The memory point is huge.

For your decently coded basic webserving app, the bottleneck is almost never
your code. It is the network, databases, etc. And in my experience, the bound
on how many simultaneous requests you can serve is generally not your CPU. It
is how much memory it takes to have enough processes ready to handle requests.
(Admittedly my experience is mostly with Perl, where multi-threaded code is a
bad idea. But Python has the GIL, so again I would plan on using more
processes instead of heavily multi-threaded code for concurrency.)

Given this, for the kinds of web applications that I have worked on, reducing
memory consumption is the thing that most helps scalability.

------
PaulHoule
Memory consumption for code isn't usually a big issue these days -- at least
if you're programming on a mainstream computer or well-powered tablet. (Memory
consumption of data keeps me up at night, but that's another issue.)

I've seen that some people are concerned about webapp memory consumption, and
that might be very real for CGI scripts (I remember memory being primary in
1999), but working with PHP, Java and ASP.NET, I see code size as a secondary
concern. Facebook developed HipHop because CPU time really is the bottleneck
with PHP; and I can say that when I need to upgrade a busy PHP box it's
nowhere near running out of memory.

Now there are embedded systems, and sometimes there it's worth making radical
efforts to shoehorn things. Some people do it for work, some people do it for
a hobby, but if I'm programming for fun I've got better things to do than
scrutinizing my toolchain to squeeze out everything that wastes memory so I
can run Linux in 8MB of RAM.

~~~
Periodic
For a major site with any sort of budget, RAM is a secondary concern. For my
small, personal sites that have very little traffic I want as little memory
usage as possible. Having a DB server, web server, a few websites including
some that run outside Apache and are proxied to (e.g. mongrel) I find myself
easily hitting the 256-512MB limits of cheap virtual machines. Upgrading to
the next highest tier can sometimes mean doubling my monthly bill. I don't
even think of running Java servers.

This isn't a major concern, but I wanted to point out that anyone on a budget
will want memory usage to at least be reasonable. It impacts our ability to
experiment and play if we are bound by memory constraints.

~~~
PaulHoule
The memory consumption of data ~is~ a big issue for the web sites I build
because it makes the minimum increment of capacity on AMZN web services
larger.

Memory consumption of code just isn't an issue because my data is at least
10,000x bigger than the code

------
vgnet
Author has recently submitted a patch to CPython which he believes will speed
up the interpreter (looks too invasive and not discussed enough to have a
chance of landing anytime soon IMHO): <http://bugs.python.org/issue14757>

------
courage
The time required to JIT compile code apparently has a big effect on web
browser JavaScript performance. Web pages often have a lot of cold code that
isn't run often.

JavaScriptCore is getting a new fast interpreter that will be the first choice
way to run code that hasn't proven hot:
<http://trac.webkit.org/changeset/108309>

~~~
justincormack
Well there certainly is a lot of scope to make a faster interpreter, as eg
according to <http://news.ycombinator.com/item?id=1187901> the Luajit
interpreter is faster than compiled v8...

~~~
cygx
I'd be surprised if that's still true today. V8 used to be consistently slower
than LuaJIT, but has caught up since then.

~~~
justincormack
Would be interested in an update, but was actually referring to Javascript
interpreters, which were very slow.

------
tikhonj
I'm curious--by what criteria is Java a "lower" level language than, say
Python? You don't really get more access to the machine with Java than with
Python and both have similar reflection capabilities. Java is obviously
statically typed where Python is dynamically typed, but that is orthogonal to
how low-level a programming language is.

~~~
lihaoyi
Java's arrays[] and primitives are part of the underlying implementation
poking through with special syntax and behavior, as compared to
arrays/lists/numbers in python/c#/scala which are full fledged objects.

That's basically the crux of it. In python/c#/scala, a for loop looks the same
whether you use ints, longs, bigInts or complex numbers as the iterator. In
java, you have special, relatively compact syntax for the close-to-the-metal
types like ints or longs, but using anything higher level suddenly becomes
ridiculously verbose. Writing BigIntegers as for-loop iterators is no fun!

------
anuraj
The best dynamic code compiler out there is Java Hotspot VM. Your
demonstrations are just based on Python runtime. Please do a comparison with
Java Hotspot for credibility.

------
jules
This only applies to languages of the Ruby/Python/Lua/Javascript/etc type. If
you remove a bit of dynamicity for metaprogramming in favor of compile time
metaprogramming, then compilation becomes both simple and has huge speedups.
As an example, see Lisp/Scheme/Clojure.

~~~
pwpwp
(Common) Lisp has at least as much runtime dynamicity as the scripting
languages you mention. It's hard to quantify, but I have the feeling that CL
errs even more in favor of dynamicity than e.g. Python and JS.

~~~
xyzzyz
_It's hard to quantify, but I have the feeling that CL errs even more in favor
of dynamicity than e.g. Python and JS._

Especially if you include non-ANSI stuff like metaobject protocol or
environments.

