[Grace-core] fixing REPL's

Lex Spoon lex at lexspoon.org
Tue Apr 30 19:37:22 PDT 2013


On Tue, Apr 30, 2013 at 8:08 PM, Kim Bruce <kim at cs.pomona.edu> wrote:
> The way Haskell (and many/most? languages with REPL's) works is that each new line opens a new embedded scope.  That is, the above code is interpreted as
> let sqr x = x * x in let sqq x = (sqr x) + 1 in let sqr x = x + 1 in sqq 5 end end ...
>
> (I left out the first call of sqq 5 for simplicity).
>
> We should treat the Grace REPL similarly.  Each new line should enter the new definitions in a new embedded scope.  Now, I guess we don't have simple mechanisms for opening new scopes, but it seems like we could just be adding new object scopes at each new line:
> object{
>        method sqr ...
>        object {
>              method sqq x = ...
>              object {

I used this nested scopes approach for the Scala REPL. I remember
thinking quite hard about how to frame what a REPL is doing, and
nested scopes made the whole thing click. The approach has a lot to
recommend it: you get a clean and intuitive semantics, it does the
right thing in as many cases as a designer can hope for, and it
doesn't need any big new compiler internals.

As a few implementation notes:

- You can get the nested scopes effect by using imports. Give those
objects a name like $replWrapper123, and when compiling something new,
import the symbols from old scopes. This way, the compiler and runtime
see a sequence of completely legitimate programs, rather than needing
to develop and refine a notion of incomplete objects.

- There's no need to import *every* prior definition. Do an initial
parse of what they write to figure out what symbols the code might be
referencing. Import just those. This would also solve the question
about having same-named definitions in the same scope.

- Users don't want to see the object wrappers. A simple and effective
way to hide the object wrappers is to name your wrappers conveniently
and do some regex-based replacements on the program's stdout. This was
another tough decision. In short, while regex-based rewriting feels
gross, it's very thorough, and I'm not sure it is less gross than the
alternatives.

I will admit, redefining earlier definitions would sometimes be
helpful for a user. Redefinition works beautifully in Scheme,
Smalltalk, Emacs Lisp, and Self, and people rave about it. In a typed
language, though, implementing it looks very hard. What do you do
about definitions that are downstream from the one you are changing?
Surely you cannot insist that they all continue to type check. I guess
you have to invalidate them, but it's tough to see how. When you
invalidate, what do you do about existing state that might refer to
them? Also, what if the user fixes the old definition and brings
everything back into consistency? Do you provide a way to bring back
the things you previously invalidated? If you do so, do you bring back
the associated state, too?

I really don't know, so I went with nested scopes.

Lex


More information about the Grace-core mailing list