Skip to content

Instantly share code, notes, and snippets.

@allenwb
Forked from dherman/short-functions.js
Created March 11, 2012 08:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save allenwb/2015544 to your computer and use it in GitHub Desktop.
Save allenwb/2015544 to your computer and use it in GitHub Desktop.
using do () {} for concise functions
// 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)
@rauschma
Copy link

It’s a bit like adding parameters to a do block delays its execution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment