[E-Lang] the return of `return'

Dean Tribble tribble@e-dean.com
Mon, 18 Jun 2001 22:03:41 -0700

I couldn't resist sending a message because this problem is so easily 
addressed with the Scheme concept of syntactic environments.  I first 
encountered the concept of syntactic environments (closures, scopes, etc.) 
in Scheme work to *correctly* expand macros (called hygienic macro 
expansion).  However, it applies to *any* program transformation work, and 
so is an important concept for understanding transformation of E into 
kernel-E, for example.

Also note that I'm not advocating changing anything.  Syntactic 
closures/environments are just a concept that so completely solves a 
problem that was really hard for sooo long, that I had to pipe up.  I 
should also mention that the Joule compiler uses syntactic environments, 
both correct macro expansion, and to connect uses of names with their 
definitions, even if the definition is later in the same block.

BTW I also agree with Jonathan and zooko's comments, but only had useful 
input here, so I'm replying to this message.

>Or we could have a `return' that returns immediately.  That creates
>several problems.

I will first give a bit of background on syntactic environments, and then 
relate it to the problem at hand.  The syntactic environment of an 
expression is literally based on the textual location in which it 
occurs.  If a macro defined in place A is expanded in place B, and it uses 
X and defines Y, then the X that it uses is the X definition visible at the 
macro defining location A, *not* the macro use location B (even if the one 
at B is visible in the lexical scope).  Similarly, if the macro is used 
(expanded) in a location with a Y visible and the sexprs nested within the 
macro appear to use Y, they will use the Y visible from B, not the 
intervening Y defined by the macro (which has A as its syntactic 
environment).  Think of it as alpha-renaming happening before macros 
expand, where the tag on the new name designates the syntactic environment 
of the use or definition.  Just for clarity, a brief scheme example:

         (define x 3)
         (defmacro (maxx s t) (* s (max t x))
         (let ((x 4) (s 2) (max "Maximillian"))
            (maxx x s)))

evaluates to 12 (whereas with CPP it would crash because "Maximillian" is 
not a function...).

Everything I've described so far is about *hygienic* macro expansion (or 
code transformation).  A macro can be non-hygienic (meaning that variables 
can be inappropriately captured);  it can explicitly define or reference a 
variable within the syntactic scope of another expression (typically, the 
expression to which the macro is applied at expansion time).  This allows 
us to specify what the various escape constructs in E do.  For instance, 
loops define the "break" and "continue" functions not just in the lexical 
scope of their body, but also in the syntactic scope of their body.  Thus, 
they capture those references.

>First, E has nested definitions -- which method do we return from?
>Suppose it's the innermost.  Then
>    for key => value in collection { ... return(x) ... }

Method definition would define "return" in the syntactic scope of its 
method body.  Remember, it is still just a simple variable definition after 
alpha renaming, and so would be a legal E expression.  Thus,

>    collection iterate(def _(key, value) { ... return(x) ... })

wouldn't have a problem, as I will show in the example below, because the 
reference to "return" would have been renamed to the "return" escape 
defined by the function definition it was textually embedded in, not the 
function definitions newly introduced by transformation (which would have a 
different syntactic scope).

>This could be fixed by giving `for' an uglier expansion.  Or,
>we could change the definition syntax to explicitly name the return
>escaper, like
>    def foo(collection) return {
>        for key => value in collection { ... return(x) ... }
>    }

Naming the escape is unnecessary.  Changing your expansion to just be the 
generic for a function definition:

    def foo(collection) :any {
        escape return_34 {
            for key => value in collection { ... return_34(x) ... }

Here, the syntactic scope of the function body was designated 34 (i.e., the 
compiler bumped a counter for every new syntactic  scope), so all names 
introduced in that scope have "_34" appended after renaming, but are 
entirely legal E expressions.

Note that if "return" is required (which I prefer), the :any declaration is 
redundant; it should be integrated with the introduction of the return.

>Second, in E we'd expect return to be, like break and continue, a
>locally-bound name for an escaper function -- while in the C world
>it's special syntax.  C programmers will try to write `return 42'
>instead of `return(42)' and be annoyed when it doesn't work.  We could
>add special syntax for return, break, and continue, then.  (Holding my
>nose here.)

If you want compatibility, that's what you would need to do.  It is 
sufficiently more attractive and familiar that it is worth it.  It is also 
preferable because then you don't need non-hygienic transformation to kernel E:

>    def fooMaker(x) {
>        return(def foo {
>            to blat() { ... }
>        })
>    }

With special syntax, the above would be:

    def fooMaker(x) {
        return def foo {
            to blat() { ... }

or perhaps even

    def fooMaker(x) {
        return foo {
            to blat() { ... }

which expands to:

    def fooMaker(x) {
         escape exit_3 {
            exit_3(def foo {
                     to blat() { ... }

Which immediately and trivially optimizes to

    def fooMaker(x) {
        def foo {
            to blat() { ... }

in kernel E, which is what you would expect.  Actually, you wouldn't even 
insert and escape unless there was an embedded reference to it.

Return guards:  In this scheme, return guards would describe the expression 
to apply when the escape function is invoked.  No guard means no additional 
function.  A s a result, there would no longer be an implicit guard, but 
rather an implicit return null unless you escape.

Other constructs:  Note that several other syntactic constructs in E could 
use a little syntactic closure(s) :-)  The general elegance heuristic is 
that you should not need non-hygienic macros.  Thus, instead of saying "def 
foo__uriGetter" and its corresponding "<foo:...>", there should be a 
construct like "defURIs" defined in the same syntactic scope as the 
"<...:...>", such that:

defURIs foo (uri) {...}

expands to

def foo__uriGetter...

in the same syntactic context as the rest of the magic data syntaxes.