[E-Lang] scope rules

Darius Bacon darius@accesscom.com
Mon, 18 Jun 2001 03:42:32 -0700


In March, Ralph Hartley complained about the awkwardness of mutual
recursion in E, and advocated a different scope rule where definitions
are in scope starting from the `{' they're immediately nested within.
Mark Miller answered

> This proposal makes me queasy, but it's simpler than what we've got,
> and I don't actually see anything wrong with it yet.

and the thread ended there.  I'll try to add some analysis.

A technical objection: what do we do at the interactive toplevel?  If
we treat an undefined variable as a forward ref to its future
definition, then a mistyped variable name only shows up as an
unresolved promise, which is confusing.  Perhaps a warning on its
first occurrence is in order.

I think when Java programmers think of definitions as declarative (and
hence order-independent) it's because they have a compile-time/runtime
split in mind: declarations are compile time, statements are runtime.
>From this viewpoint, order independence is natural to the first but
not the second.  (I'm trying to think like a random Java programmer,
not Ralph Hartley.)  This is a way E can feel unnatural, because its
definitions are all the same kind -- so our rules can make them seem
either declaration-like or statement-like but not both.

So where will people expecting statement-like get confused by
declaration-like scopes?  In C++ it's possible to have

   void stuff(int foo)
   {
     ... foo ...
     int foo = newFoo;
     ...
   }

The corresponding E code under the Hartley rule would make the first
foo reference be a promise for newFoo, not the foo argument as in C++.
This looks like a case of a programmer asking for trouble, and I can't
see any situation less esoteric that brings it up.  In Java, this code
is illegal.

Is this rule declaration-like enough to really be unsurprising?  It's
not truly order-independent because the order determines when the
promises are resolved.  Let's see... say someone's unsatisfied with
E's object model and writes a package supplying `hyperclasses'.  A
user of the package writes

   def MyHyperclasses := {
       def A := makeSubclassOf(B, ...)
       def B := makeSubclassOf(null, ...)
       [A, B]
   }

and it throws an exception because makeSubclassOf(B) needs to resolve
B to do its setup work, for some reason.  The user complains ``Huh?  I
need to topo-sort my classes by hand?  But these are declarations!
Anyway, why didn't the compiler complain?''  Nope, I'm not finding
this problem very compelling -- the biggest issue is it doesn't catch
the error as soon.

Another way it might not be declaration-like enough: declarations in
the C world don't produce values, while E definitions do.  If you tell
people the order of definitions doesn't matter, they might write

  def fooSquared(x) :any {
      f * f
      def f := foo(x)
  }

expecting f*f as the result.  That's pretty farfetched -- a Haskell
hacker would be likelier to write that than the programmer off the
street.

How should this expand into Kernel E?  Kernel E should keep the
restriction that definitions appear before uses.  The only change I
see is the scope analysis phase needs to insert promise definitions
before the uses that were out of scope before.

Finally there's the question how this restricts future evolution of
the language.  Maybe it wouldn't play as nice with a module system --
I haven't thought about it.

How important is this change to make E more comfortable to beginners?
Not terribly, I think.  It still makes me uneasy, too -- am I really
claiming this will feel *more* familiar?

-Darius