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

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

On Sun, Dec 21, 2008 at 6:22 AM, Dave Herman <dherman at> 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

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

    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 <>,
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".

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <>

More information about the Es-discuss mailing list