[e-lang] Scope restriction on Macros
Mark Miller
erights at gmail.com
Thu Sep 28 21:06:02 CDT 2006
On 9/27/06, Brandon Moore <brandonm at yahoo-inc.com> wrote:
> Thinking about scoping, I reviewed the note on E macros at
> http://www.erights.org/data/irrelevance.html
>
> I don't see exactly what restriction is being proposed. I'm wondering
> whether desugaring
> let x = exp in exp2
> to
> (fun x {exp2}) (exp)
> is an error even if exp doesn't actually mention x.
To avoid issues I don't think you meant to raise, let's assume the
pre-expansion syntax was
let x = (exp) in {exp2}
The pre-expansion scope analysis says there's a scope box that starts
at the "let" and ends at the close curly. The "x" visible above is a
defining occurrence of "x". The left-to-right order in the scope box
is as shown, so this "x" is in scope in exp and exp2, and any further
identifiers bound on exp are in scope in exp2.
To make the expansion proper E, it'd be "(fn x {exp2}) (exp)". To
avoid other issues I doubt you meant to raise, let's place curlies
around that in the expansion as well, so let's say the above expands
to
{(fn x {exp2}) (exp)}
If exp doesn't mention "x", and exp does not define any variables used
by exp2, and there are no names for which both exp and exp2 define
variables, then the original would be accepted and would expand as
above without an error. When any of these conditions are violated,
then the original is statically rejected, because the pre-expansion
scope analysis yields different bindings from the post-expansion scope
analysis.
> It's a mismatch if
> you compare the actual set of bindings in effect at each expression, but
> not if you only look at what binds the variables that actually appear in
> the code. I'd recommend the latter, if that isn't already what you
> meant.
I'm sorry, but I don't understand what you mean by the latter. Could
you please clarify? Thanks.
> Perhaps you could improve on this a bit, by letting the macro
> explicitly declare that the binders from some pattern shouldn't be
> mentioned at all in a particular expression, and provide a more specific
> error message when things go wrong.
I may not understand what you're suggesting, because I don't see how
(what I think I understand) would help.
> This reminds me a bit of IF logic:
> http://planetmath.org/encyclopedia/IFLogic.html
Could you give a less mathematical summary of this work?
> On to other things:
>
> (I'd like to note that Haskell follows a left-to-right binding order in
> the case of lets, because it does let a name scope over its own
> definition, and relies on lazy evaluation to sort it out.)
E does something similar for def expressions, using promises instead
of lazy evaluation:
? interp.setExpand(true)
? def x := [1, x, 3]
# expansion: def [x__1, xR__3] := Ref.promise()
# def res__5 := def x := __makeList.run(1, x__1, 3)
# xR__3.resolve(x)
# res__5
# value: [1, <***CYCLE***>, 3]
> The thinking about scoping I was doing concerned using lambdas and
> ordinary functions to emulate/provide simple scope-building sugar.
> As a quick example, for loops could be used as:
>
> foreach [0..3] \x ->
> foreach [0..5] \y -> do
> ... stuff with x,y ...
>
> ("\var1 var2 .. varN -> body" is Haskell for lambda, and
> "do" introduces a block of statements)
? pragma.enable("lambda-args")
The above turns on the experimental "lambda-args" syntactic extension,
which allows the use of scope-respecting control structure syntax to
invoke user defined control abstractions.
? def __for := <elang:control.__for>
The above imports a foreach-like control abstraction, which we use to
express your example below:
? __for (0..3) each x {
> __for (0..5) each y {
> println(`[$x,$y]`)
> }
> }
[0,0]
[0,1]
[0,2]
...
In the expansion of this, each "each" clause is packaged up as a
closure and sent as an argument in a message to the __for object. This
expansion exactly preserves source scope relationships, so the above
expansion-surprise-reject issues don't arise.
> Maybe it's worth including some syntax like this to make it easy to
> provide simple scope-handling things, and to provide a uniform
> appearance for those things.
Does the above "lambda-args" syntax provide what you need? If so,
that'd be great, because it's much cleaner than introducing
user-defined macros into E.
> (It does work especially nicely in Haskell because using monads to
> control side effects means ordinary can decide if, when, and how often
> the side effects of evaluating their arguments occur).
E's perspective on side effects is more conventional -- ML and Scheme
-like rather than Haskell-like. However, because these clauses are
given to __for, for it to invoke, __for is in control of when and how
often the side effects in these clauses are run. In this regard, E's
lambda-args syntax provides much of the same flexibility as
Smalltalk's blocks, which inspired them.
The src/esrc/org/erights/e/elang/control/ directory in the
distribution contains various similar experiments in user-defined
control structures.
--
Text by me above is hereby placed in the public domain
Cheers,
--MarkM
More information about the e-lang
mailing list