-
-
Save allenwb/2015544 to your computer and use it in GitHub Desktop.
using do () {} for concise functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 1. No new syntax for non-TCP functions. | |
// | |
// This approach does do include a shorter syntax for regular functions, so if a classic JS function | |
// is what you want you use the classic long form function expression: | |
a.some(function (x){ | |
if (invalid(x)) | |
return true; | |
console.log(x); | |
}); | |
// However, this form is fully compatible with another proposal for explicit opt-in | |
// to using 'fn' as an abbreviation for 'function'. use fn; also implies "use strict". | |
use fn; | |
a.some(fn (x){ | |
if (invalid(x)) | |
return true; | |
console.log(x); | |
}); | |
// this is nice because everybody will probably want to use "fn" so it will be a carrot for using strict mode | |
//if you see a function expression starting with 'function' or 'fn' it is a classic | |
//no-TCP, dynamic this-binding function definition. | |
// 2. Lambdas. | |
// | |
// Lambda expressions that support the TCP all begin with the word "do" and have a | |
// parenthesized argument list. The body may be either an assignment expression (restricted to not starting | |
// with "{") or a (possibly labelled) block statement. Between the "function" and "return" keywords, | |
// this saves a ton of noise and rightward drift. Just like | |
a.map(do (x) x * 17) | |
// Note that because the expression that supplies a do body is an assignment | |
// expression, the following passes two arguments to the reduce function: | |
a.reduce(do (accum,x) x+accum, 0); | |
// If the body of of the lambda expression requires multiple statements it is coded | |
// as a block: | |
a.reduce( | |
do (accum,x){ | |
if (x<0) accum-x; | |
else if (x>0) accum+x; | |
else this.baseValue+x; | |
}, 0); | |
// Unlike normal functions, do lambdas respect Tennent's correspondence principle: | |
// In particular, the this-binding remains the same as in the outer context. | |
CSVReader.prototype.read = fn(str) { | |
return str.split(/\n/) | |
.map((do (line) line.split(this.regexp)); | |
}; | |
// TCP also applies to any control transfer statements that occur within the body of | |
// of a do lambda. | |
function f(str,b) { | |
let lines=0; | |
let newStr = "" | |
for (let line of str.split(/\n/)) { | |
lines++; | |
line.foreach( | |
do(c) { | |
if (invalidChar(c)) return [-lines,newStr]; //returns from f | |
if (c == ";") continue; // continues with next for-of iteration | |
if (c == ctlZ) break; // break from for-of loop | |
newStr += c; | |
} | |
); | |
} | |
Console.log(newStr); | |
return [lines,newStr]; | |
} | |
// There is more about continue and break statements in do lambdas in section 4 below | |
// | |
// I believe that the do () {} form is superior to the ()=>do {} form because the leading "do" gives the human | |
// code reader an early indication of exactly what they are reading. | |
//this can be seem with simple do lambdas. Contract the following two lines: | |
let obj = f(do(a,b) ({x:a, y:b})); | |
let obj = f((a,b)=>do ({x:a, y:b})); | |
// It is even more apparent when the do lambda formal parameters are complex forms. In such cases it may | |
// take several lines before it becomes syntactically apparent to a human reader. Consider these two | |
// alternative expressions of the same thing: | |
let obj = f(do({x:a,y:b}, | |
{x:c,y:d}) | |
({x:a+c, y:b+d}) | |
); | |
let obj = f(({x:a,y:b}, | |
{x:c,y:d}) | |
=>do ({x:a+c, y:b+d}) | |
); | |
// 3. Immediate Do-expressions. | |
// http://wiki.ecmascript.org/doku.php?id=strawman:do_expressions for original proposal | |
// Syntactically an immediate do-expression looks like a do lambda with a block body | |
// but missing the formal parameter list: | |
let obj = do { | |
const _foo = Name.create(); | |
({ | |
[_foo]: undefined, | |
get foo() {return this._foo}, | |
set foo(v) {this.foo=v} | |
}); | |
}; | |
// Another way to think about an immediate Do-expression is that it is a equivalent | |
// to an immediately invoked 0-arguent do lambda: | |
let x = do {whatever }; | |
// is equivalent to: | |
let x = do () {whatever } (); | |
//4. TCP and control abstractions | |
// An important use case of do-lambdas with TCP is to supply the bodies of | |
// program defined control abstraction. However, if we stopped with | |
// do lambdas as defined so far, such control abstractions would still be | |
// second class citizens in comparison to the built-in control statements | |
// because the continue and break statements could not be used to control | |
// iteration. | |
// What we really want to be able to do is to directly refactor code such as: | |
for (let v of a) { | |
if (v == value1) continue; | |
if (v == value2) break; | |
if (v == value3) return; | |
doSomethingWith(v); | |
}; | |
// Into something like: | |
a.forEach(do (v) { | |
if (v == value1) continue; | |
if (v == value2) break; | |
if (v == value3) return; | |
doSomethingWith(v); | |
}); | |
// Without changing anything in the body of the loop. | |
// We already have to do some work to define the semantics for continue/break/return | |
// with do lambdas. With just a little extra effort we can enable TCP behavior for | |
// such user defined control abstractions. | |
// continue/break/return control transfers normally follow the lexical structure of | |
// the program and each ES looping construct defines specific semantics for what to | |
// do when a continue or break occurs within the its body propagates to its | |
// lexical level. For user defined looping abstractions to have comparable behavior | |
// they have to be able to define their own semantics for any continue or break that | |
// occurs within their lambda valued arguments. | |
Array.prototype.forEach = fn(callBack, thisArg) { | |
let index = 0; | |
let completion = {value: undefined]; | |
impl: while (index < this.length) { | |
completion = callBack.callDo(thisArg,this[index],index++,this); | |
switch (completion.kind) { | |
case "break": | |
break impl; //out of implementation while loop | |
case "continue": | |
default; | |
continue impl; //next iteration,not really needed here | |
} | |
return completion.value; //return value from last iteration completion object. | |
} | |
// callDo is a new method of Function.prototype. Its formal parameters are the same as | |
//the call method and its semantics are nearly the same except that it returns a | |
//completion object instead of the actual return value return by the function. | |
// | |
// A completion value is an object that looks like this: | |
// Object.prototype <| { | |
// kind: String, //"normal", "continue", break" | |
// value: Object, //the return/continue/break value | |
// implicit: Boolean //true if a break/continue didn't have a target label | |
// propagate() { | |
// /* built-in function that propagates continue/break to next level */ | |
// } | |
//callDo can be applied to either a normal function or a do lambda. The first | |
// argument (thisValue) is ignored for do lambdas. Normal functions always return | |
// a completion object of whose kind is "normal". There is also a corresponding | |
// applyDo method. | |
//Here is another use of callDo: | |
Collection.prototype.forEachAlternating = fn(callBack1, callBack2) { | |
let first = true; | |
return this.forEach(do (v) { | |
let completion = (first?callBack1:callBack2).callDo(this,v); | |
switch (completion.kind) { | |
case "break": | |
break /*forEach*/; | |
case "continue": | |
default; | |
first = !first; //next iteration will use other callBack | |
continue /*forEach*/; | |
}); | |
} | |
// Not every control abstraction necessarily wants to deal with completion objects, | |
// only those that apply special meaning to break/continue. For example: | |
Boolean.prototype.ifElse = fn (trueBlk,falseBlk){ | |
if (!!this) return trueBlk(); | |
else return falseBlk(); | |
} | |
//(note that a normal [[Call]] of a block propagates the completion value to any outer | |
//lexical contexts) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It’s a bit like adding parameters to a
do
block delays its execution.