Why:
function.lambda are useful for:How:
Add four productions to the grammar:
Expression ::= ... | lambda Formals Block
| lambda Formals Expression
Declaration ::= ... | lambda Identifier Formals Block
| lambda Identifier Formals Expression
Or possibly just one single production:
Expression ::= ... | lambda Formals Statement
This would allow for an expression body since ExpressionStatement is a production of Statement. Unsure whether this causes any insurmountable ambiguities.
What:
lambda is the primitive form. The expression form desugars to a block-lambda with just the expression in its body.lambda may take rest-args only with an explicit rest-parameter (e.g., ...rest). Otherwise it is a dynamic error to call the function with too few arguments.lambda may take optional arguments and may have destructured parameters.lambda is the completion value of its body.lambda must be properly tail calling: a procedure call in tail position must consume no more space than it would consume to return from the caller and call the callee.if, the second subexpression of an || expression, etc.switch bodies, try bodies, the test expression of an if, etc.return, arguments, or this for a lambda body. These all keep the same meaning that they had in the immediate context of the lambda.var scope for a lambda body. Any var declarations still hoist to the nearest enclosing function.functions, labeled break and continue statements may cross over lambdas. Like with return to label, if a break or continue dynamically attempts to jump to a dead label, an exception is raised.Examples:
Several functional styles using lambda:
lambda fact(n) { if (n === 0 || n === 1) 1 else n * (fact(n-1)) } lambda factIter(n, acc) { if (n === 0 || n === 1) acc else factIter(n - 1, n * acc) } lambda factCPS(n, k) { if (n === 0 || n === 1) k(1) else factCPS(n - 1, lambda(result) k(n * result)) }
Desugaring the function expression function(x1,...,xn) Body requires simulating the behavior of this, arguments, and return. The latter takes advantage of return to label, but could also be implemented with exceptions. Since lambdas and functions are called with the same calling mechanism, we can’t just pass the value for this as a parameter. So the function call protocol saves the this value in a variable thisRegister. The implementation of arguments is described below.
lambda(x0,...,xn,...$rest) { let $THIS = thisRegister; let arguments = makeAlias([[lambda() x1, lambda($x1) x1 = $x1], ..., [lambda() xn, lambda($xn) xn = $xn]], $rest); $RETURN: { Body; void 0 } }
Then return e desugars to return : $RETURN e and this desugars to $THIS. Note that both $RETURN and $THIS should be gensymed once for the whole program, i.e., they should not be capturable by user code but should be the same in every instance of the desugaring.
The thisRegister variable is set by the function-call protocol. A function call e.m(e1,...,en) desugars to
let ($obj = e, $t1 = e1, ..., $tn = en, $method = $obj.m) (thisRegister = $obj, $method($t1, ..., tn))
Notice that thisRegister is trashed as the very last action — even after the method dereference, in case the dereference invokes setter code — before calling the method.
The makeAlias helper produces an array-like object whose indexed properties behave like aliases for the function parameters. Its implementation looks more or less like so:
lambda makeAlias(params, rest) ({ get *: lambda(i) { if (!isNumeric(i)) // etc. else if (i < 0) // etc. else if (i < params.length) params[i][0]() else { i -= params.length; if (i < rest.length) rest[i] else // etc. } }, set *: lambda(i, x) { if (!isNumeric(i)) // etc. else if (i < 0) // etc. else if (i < params.length) params[i][1](x) else { i -= params.length; if (i < rest.length) rest[i] = x else // etc. } } })
Issues:
let-binding at function top-level and a let-binding in a nested scope, then the Body becomes a nested block and any let-declarations inside of it have the wrong meaning. One fix is to ensure that let-declarations are no different at function top-level than in nested blocks (i.e., they should shadow function parameters).function above doesn’t address var-hoisting and the effect of function boundaries on break and continue labels.