Block exprs as better object literals (was: Semantics and abstract syntax of lambdas)

Mark S. Miller erights at google.com
Sun Dec 21 13:53:39 PST 2008


On Sun, Dec 21, 2008 at 6:22 AM, Dave Herman <dherman at ccs.neu.edu> wrote:

> Lex Spoon wrote:
>
> So I would be interested in a simple syntactic form like Lex's suggestion.
> Imagine for a moment the following idea didn't cause parsing problems (it
> does, but bear with me). Say we had a sequence-expression form:
>
>    { stmt; ... ; stmt; => expr }
>
> and then add two kinds of block literals, a block-statement form and a
> sequence-expression form:
>
>    ^(x1,...,xn) { stmt; ... ; stmt }
>    ^(x1,...,xn) { stmt; ... ; stmt; => expr }
>
> 1. Tail position would only be a property of *expressions*, not statements
> [1]. That's simpler.
> 2. There's no "accidental return" hazard because you have to explicitly use
> the => form.
> 3. It doesn't break Tennent because the return position is a fixed part of
> the syntax, not a separate operator whose meaning gets captured by lambda.
>
> It does make lambda-as-control-abstraction a few characters more expensive.
> More immediately, this notation is obviously wildly conflicting with the
> object-literal syntax. I probably should leave it to the syntax warriors to
> figure out how to spell this in unambiguous ASCII. But there's the idea.
>


To postpone debate about concrete lexical syntax while I make a different
point, I will use "reveal" as a keyword meaning approximately what your "=>"
means above; and "lambda" for your "^" above. Given

<block> ::= "{" <statement>* "}"
<statement> ::= ...
|        <declaration>
|        <expr>
|        "reveal" "(" <expr> ")"
<expr> ::= ...
|        "lambda" <paramList>? <block>
|        "let" <initList>? <block> // let(...){...} desugars to
(lambda(...){...})()

The notion of "revealed value" would replace the role of completion value
from your original lambda proposal. Revealing a value is not a control
transfer. It merely stores that value to become the revealed value of that
block. A "reveal" in a sub-block does not effect the revealed value of the
containing block, and may well be a static error. ("completion value" would,
unfortunately, continue to exist for legacy compatibility of uses of "eval",
but would have no other role in the language.) Your two examples above
become

    lambda(x1,...,xn) { stmt; ... ; stmt } // reveals nothing, i.e., like
"reveal (undefined);"
    lambda(x1,...,xn) { stmt; ... ; reveal (expr) } // reveals value of expr

The "reveal" would not need to go last, but a given block could have a most
one: "reveal" "(" <expr> ")".

At <https://mail.mozilla.org/pipermail/es-discuss/2008-November/008185.html>,
Peter Michaux makes an interesting proposal that I think can be combined
with the proposal above, by having "reveal" also do a job similar to Peter's
"public". Block-expressions with "reveal"s, could then be used instead of an
enhanced object literal for class-like instance declarations. That's why I
placed the mandatory "("s in the above use of reveal, so I could make more
use of this keyword without ambiguity below. An alternate concrete syntax
could use two keywords (perhaps "public"?) or other syntax of course.

<declaration> ::=
         "var" <ident> ("=" <expr>)?
|        "const" <ident> (":" <expr>)? "=" <expr>
|        "let" <ident> (":" <expr>)? "=" <expr>
|        "function" <ident> <paramList> <block>
|        "reveal" <declaration>
|        "reveal" <ident> <paramList>? <block>
             // desugars to[1] "reveal" "const" <ident> = "lambda"
<paramList> <block>
             // except that the revealed property in non-enumerable

The last two productions introduce the notion of a "revealed declaration". A
given block can either
* reveal nothing (equivalent to "reveal (undefined)"),
* have exactly one "reveal" "(" <expr> ")",
* or have any number of revealed declarations

If a block contains revealed declarations, then the block's revealed value
is a new non-extensible object whose properties mirror these declared
variable names. A revealed "const" is simply a copy of the same value. For
"var", "let", and "function", the property is an accessor property without a
setter, whose getter gets the named variable. Revealed "function"s and the
last (method) production create non-enumerable properties. The others are
enumerable. Revealed "var"s create configurable properties. The others are
non-configurable. Thus, in the absence of a revealed "var", the revealed
object is frozen. Redoing Peter's example[2], we get

const Point = lambda(privX, privY) {
  let privInstVar = 2;
  const privInstConst = -2;
  reveal toString() { reveal ('<' + getX() + ',' + getY() + '>') };
  reveal getX() { reveal privX };
  reveal getY() { reveal privY };
  reveal let pubInstVar = 4;
  reveal const pubInstConst = -4;
}

Revealed declarations always desugar to object creation + property
definition + revealing the created object. So the above example would
desugar to

const Point = lambda(privX, privY) {
  let privInstVar = 2;
  const privInstConst = -2;
  const toString = lambda() { reveal ('<' + getX() + ',' + getY() + '>') };
  const getX = lambda() { reveal (privX) };
  const getY = lambda() { reveal (privY) };
  let pubInstVar = 4;
  const pubInstConst = -4;
  reveal (Object.preventExtensions(Object.create(Object.prototype, {
    toString: {value: toString},
    getX: {value: getX},
    getY: {value: getY},
    pubInstVar: {get: lambda{reveal (pubInstVar)}, enumerable: true},
    pubInstConst: {value: pubInstConst, enumerable: true}
  })))
}

Not yet addressed:
* decent concrete lexical syntax
* how can we name the "self" object being initialized?
* how can we provide an alternative to Object.prototype to inherit from?
* how can we simply express revealing a setter as well?

The last three bullets can obviously be handled manually by avoiding the
revealed declaration sugar. But typical class-like use will want to combine
the convenience of revealed declarations with at least self-naming and
alternate parents.

There may well be a fatal flaw with this proposal *before* we bikeshed its
concrete lexical syntax. Let's try to discuss abstract syntax, semantics,
and the last three bullets above before we plummet into the concrete lexical
syntax bikeshed. Thanks.


[1] Under the assumption that lambda makes frozen closures. If not, then
     reveal <ident> <paramList>? <block>
should desugar to
     reveal const <ident> = Object.freeze(lambda <paramList> <block>)

[2] I substituted "getX()" and "getY()" for Peter's "x()" and "y()". I
couldn't tell whether Peter meant "x" and "y" to be accessor properties or
accessor methods. If accessor properties, their uses shouldn't have end in
"()". If accessor methods, I find the verb-names "getX"/"getY" clearer than
"x" and "y".

-- 
   Cheers,
   --MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20081221/ee102253/attachment.html>


More information about the Es-discuss mailing list