Flattening syntactic tail nests (paren-free, continued)

Claus Reinke claus.reinke at talk21.com
Fri Apr 8 03:42:01 PDT 2011


In my previous post, I suggested tail nests as a profitable target 
for reducing reducing parens and braces that distract from 
common code patterns. I made two concrete suggestions, to
make both braces around function bodies and parens around
function applications optional.

The combination of both suggestions would allow to reduce
the nesting of parens and braces in tail nests, such as nested 
callbacks (function definition nested in function application
argument nested in ..). For the running example, this was
successful, but rather odd looking - it should be possible
to do better.

So I would like to modify those suggestions, to bring them 
more in line with Javascript practice and Harmony directions.
As a bonus, the modified suggestions simplify another common
case of nesting, namely curried function definitions.

>[mnemonic summary of old suggestions, to be modified]
>
>Suggestion 1 (optional braces around function bodies): 
>
>    '(function (..) ..)' may be used instead of 'function (..) {..}'
>
>Suggestion 2 (optional parens around function applications):
>
>    '(f @ a1, .. , an)' may be used instead of  'f(a1,..,an)'

Suggestion 2 is especially problematic if the argument list
has more than one component: the hope was that we 
might later get rid of the commas as well, assuming that
argument lists are argument tuples, which could become 
individual arguments by currying. However, I've come to 
think that this underlying assumption does not fit Javascript: 

With optional arguments and unbounded lengths, argument 
lists are really argument arrays - they do not stand for multiple 
arguments (Javascript has curried functions for that), they each 
stand for a single argument with an unspecified number of 
components. We just use them for multiple arguments because
curried function definitions are so awkward.

Harmony changes, such as formal parameter destructuring 
and spreads, will remove the differences in feature sets between
argument lists and arrays, making it possible to replace

    'f(a1,..,an)' and 'function f(a1,..,am) {..}'

with 

    'f([a1,..,an])' and 'function f([a1,..,am]) {..}'

at which point the parens will be redundant and each argument
list can be a proper array (dear arguments: I want my syntax 
back!-). So this part is well in hand already, and trying to break 
up those argument list arrays in other ways would go against 
the direction Harmony is taking.

Also, the language already provides for curried function 
definitions and applications, they just don't get used much
yet. Curried applications are easy, but curried definitions
happen to be very awkward, syntactically.

My modified suggestions take both curried functions and
argument lists as arrays into account, so they do a little less 
work on applications, making better use of existing syntax, 
and a little more work on definitions, hoping to give curried 
definitions a lift.

What I'd like is to be able to replace the horrible

    function (..) { return function (..) { .. }}

with the shorthand notation

    function (..)(..) { .. }

(and, similarly, '#(..)(..){..}' instead of '#(..){ #(..){..}}')

giving curried function definitions the same short syntax
as curried function applications. This shorthand makes it
obvious that each argument list really is just a single, 
complex argument to the function. There are a few obvious 
problems which would have made this difficult before
Harmony:

    - using the outer function's arguments pseudo-array
        in the inner function's body;
        
        Harmony's spreads avoid that problem entirely.

    - using the outer function's 'this' in the inner function's
        body (the shorthand notation no longer has an outer
        function body in which to rename 'this' to 'self'); 

        this is being addressed in other threads here, so 
        Harmony is likely to offer at least one solution
        (optional naming for the implicit argument 'this').

    - if the formal parameters can consist of multiple
        argument lists, the beginning of the function body
        is no longer unambiguous without those braces;
        to address this, we need an explicit syntactic marker
        at the boundary between arguments and body;

        from the archives, it seems that '=>' has been a
        fairly popular suggestion (usually instead of a prefix
        marker), so I'll adopt that, but without removing the
        prefix marker, be it 'function' or '#'.

Which results in the following modified suggestions

// --------------------------------------------------
// Modified suggestion 1 (function definitions): 

    1a (curried definition shorthand)   

            function (..)(..) { .. }

        may be used instead of

            function (..) { return function (..) { .. }}

        for arbitrary nestings.

    1b (function bodies)

            (function (..)..(..) => ..)

        may be used instead of 

            function (..)..(..) {..}

        (the outer parens are _not_ part of the function definition
         syntax - they indicate that the source context delimits the
         function definition from the outside: if the construct in
         which the definition is nested ends, so does the definition) 

    1c (ASI needs to know about 1b's implicit block endings)

// --------------------------------------------------
// Modified suggestion 2 (function applications):

        (f @ x)

    may be used instead of

        f(x)

    (the outer parens are _not_ part of the function application
     syntax - they indicate that the source context delimits the
     function application from the outside: if the construct in
     which the application is nested ends, so does the application) 

    '@' is now just a left-associative infix operator (same 
    precedence as function application), so

        (f @ x @ y @ z)

    is

        (((f @ x) @ y) @ z)

    which is

        f (x) (y) (z)

    (ideally, infix function application would just be juxtaposition,
      ie, the parens around single-component argument lists
      would be optional, without requiring an explicit operator)

// --------------------------------------------------

The effect on our running example, in terms of removing
redundant parens and braces, can be similar, but that
now needs support from the libraries: they would need to
curry their API functions so that the callback becomes a
second, separate argument (instead of the last component
in a single complex argument list). 

(mainWindow.menu("File") @ function(file) =>
  file.openMenu @ function(menu) =>
    menu.item("Open") @  function(item) =>
      item.click @ function() =>
        mainWindow.getChild(type('Window')) @ 
            function(dialog) =>
              ...
);

// Note: since function definitions extend as far as possible,
// to the closing paren here, the inner '@' are unambiguosly
// nested

These modified suggestions are simpler, more in line with
Harmony's directions, and help to reduce the syntactic noise
for two common sources of nesting: curried definitions and
definitions as callback arguments.

Comments, please?-)
Claus
 


More information about the es-discuss mailing list