Skip to content

Instantly share code, notes, and snippets.

@benaston
Last active November 23, 2017 11:15
Show Gist options
  • Save benaston/795b644bbbc67098ec2bd388c7060826 to your computer and use it in GitHub Desktop.
Save benaston/795b644bbbc67098ec2bd388c7060826 to your computer and use it in GitHub Desktop.

Motivation

  • Partial application is fundamental to functional-style programming
  • It is the binding of arguments to parameters before a function is invoked
  • This technique is often used by ECMAScript developers who are:
    • seeking to avoid the use of new and/or this
    • deliberately trying to avoid function genericism
    • trying to improve consistency of style
  • Partial application is currently supported by Function.prototype.bind, but the syntax is verbose, and the target of the bound function comes first, diluting the semantic
  • Writing a custom function to provide partial application is straightforward (indeed there are many open source libraries providing this such as _.partial), however developers taking this approach are constrained in their ability to achieve syntactic tenseness lest they surprise other developers
  • A terse, native syntax for partial application that does not affect the target of a function will improve consistency, clarity of intent and legibility

Proposal

Introduce two partial-application operators :>: (partially apply left-to-right) and :<: (partially apply right-to-left). These operators create a bound function such that the arguments to be supplied to the function on the left-hand side of the operator, when it is eventually invoked, are bound to those values supplied on the right-hand side of the operator.

Before

import _ from 'lodash';
const o = { 
  foo: _.partial(foo, arg1),
  bar: bar.bind(null, arg1, arg2)
};

After

const o = { 
  foo:>:arg1,
  bar:>:[arg1, arg2]
};

Before

Promise.resolve()
  .then(() => foo(arg1));

After

Promise.resolve()
  .then(foo:>:arg1);

Examples

const arg1 = 'this is arg1';
function foo(arg1) { console.log(arg1); }

// Current syntax 1
o = { foo: foo.bind(null, arg1) };

// Current syntax 2
let o = { foo: _.partial(foo, arg1) };

// Current syntax 3
let o = { foo: (...args) => foo(arg1, ...args) };

// Proposed syntax
o = { foo:>:arg1 };

o.foo(); // 'this is arg1' printed to console

Use as part of an assignment expression:

const bound = foo:>:context;

As part of a return statement with the spread operator:

function curry(foo, ...args) { return foo:>:[...args]; };

Partial application with multiple arguments. The right-hand side value may be either a single value or an array of arguments:

var bound = foo:>:['a','b'];

If the single value needs to be an array we need to disambiguate:

var bound = foo:>:(['a','b']);

Partial application from the right:

const foo = (a, b) => console.log(a, b);
var bound = foo:<:['this is b'];
bound('this is a'); // 'this is a, this is b' printed to  console

Further considerations

  • I am an unsophisticate with respect to syntax ambiguity avoidance
  • The proposed syntax is merely a first guess and I am confident there will be better ideas
  • I originally chose the syntax :: for the operator, but there is another stage 0 proposal that intends to use that syntax for a different purpose. Also the revised syntax permits identification of the binding direction (from left/from right)
@prathochup
Copy link

Already submitted to tc39 currently at stage 0 https://github.com/tc39/proposal-bind-operator

@benaston
Copy link
Author

benaston commented Mar 22, 2017

Can the operator in that proposal be used for partial application? Although I unintentionally chose the same operator syntax, I believe this strawman solves a different use-case than the link you supplied.

@Volune
Copy link

Volune commented Mar 23, 2017

Some comments to help improve the proposal:

  • Why not a Function.prototype.partial-like proposal?
  • How to write _.partial(foo, [arg1, arg2]) with this proposal?
  • Would obj.func:>:arg1 be like obj.func.bind(null, arg1) or obj.func.bind(obj, arg1)?
  • When is arg1 evaluated in the examples?

About my last point, the two following cases have different behavior, which behavior will this new operator follow?

let arg1 = 'this is arg1';
function foo(arg) { console.log(arg); }

const o1 = { foo: foo.bind(undefined, arg1) };
const o2 = { foo: (...args) => foo(arg1, ...args) };

arg1 = 'this is arg1 bis';

o1.foo();
o2.foo();

@benaston
Copy link
Author

The proposal that is currently gaining most traction is here.

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