/*===
args: 1 2 3 4
this: [object global]
args: f2 1 2 3
this: this-f2
args: f2 f3 1 2
this: this-f2
===*/

function orig(x,y,z,w) {
    print('args:', x, y, z, w);
    print('this:', this);
}

var f1, f2, f3, f4;

f1 = orig;
f2 = f1.bind('this-f2', 'f2');
f3 = f2.bind('this-f3', 'f3');

/* Plain function call, 'this' binding will be the global object. */
f1(1, 2, 3, 4);

/* Bound function: bind 'this' to 'this-f2' and prepend 'f1' to argument list:
 *
 *      f2(1, 2, 3)
 *  --> f1('f2', 1, 2, 3)  this='this-f2'
 */
f2(1, 2, 3);

/* Second order bound function: note the behavior of the this binding:
 *
 *      f3(1, 2)
 *  --> f2('f3', 1, 2)  this='this-f3'
 *  --> f1('f2', 'f3', 1, 2)  this='this-f2'
 *
 * In other words:
 *
 *   - The effective 'this' binding comes from the bound function right before
 *     the actual non-bound function (not the outermost bound function).
 *
 *   - The argument list to be prepended (for the final non-bound function call)
 *     can be formed by concatenating the [[BoundArgs]] lists of the bound
 *     functions, from innermost to outermost.  Or alternatively, by starting
 *     with an empty list, process each bound function from outermost to innermost,
 *     prepending the [[BoundArgs]].
 */
f3(1, 2);

/*===
args: 1 2 3 4
this: this-f2
args: 1 2 3 4
this: this-f2
===*/

/* Example of [[BoundArgs]] concatenation:
 *
 *    f1  <--  f2, [[BoundArgs]] = [1,2]  <--  f3, [[BoundArgs]] = [3,4]
 *
 * Process from outermost to innermost, prepending [[BoundArgs]]
 *
 *    []
 *    [3,4] + [] = [3,4]            (process f3)
 *    [1,2] + [3,4] = [1,2,3,4]     (process f2)
 *    [1,2,3,4]                     (f1 is not bound, finish)
 *
 * The ultimately effective [[BoundThis]] is the one right before the non-bound
 * function, i.e. f2's [[BoundThis]] here.
 *
 * For a "collapsed" bound function:
 *
 *    [[BoundArgs]] = [1,2,3,4]
 *    [[BoundThis]] = 'this-f2'
 *    [[TargetFunction]] = f1
 */

f1 = orig;
f2 = f1.bind('this-f2', 1, 2);
f3 = f2.bind('this-f3', 3, 4);

f4 = f1.bind('this-f2', 1, 2, 3, 4);  // equivalent to f3

f3();
f4();

