Partial argument binding with heroin

How to bind any argument by name

Recently I looked at dependency injection and wrote heroin. Mostly I targeted heroin to perform the dependency injection by name, but it can be useful to do plain argument binding. It is a nice complement to the two functions already provided by the excellent lodash library.

_.partial

If we want to bind a few arguments in left to right order, we can use _.partial that constructs a new function

1
2
var add = function(a, b, c) { return a + b + c; };
var addC = _.partial(1, 2);

addC is a function that expects a single input (value of c), it is same as writing

1
2
3
var addC = function (c) {
return add(1, 2, c);
};

I often use left to right argument binding when working with promises. Promises resolve / reject with a single value, so any additional information to be passed around needs to be bound before hand.

_.partialRight

If we want to bind arguments at the end of the function, we can use _.partialRight. We still list the values left to right though:

1
2
var add = function(a, b, c) { return a + b + c; };
var addA = _.partialRight(10, 20);

In this case b will be 10 and c will be 20, leaving the first argument a free.

heroin = _.partialSome

I prefer to bind arguments by name, rather than index. To do this, I extend lodash with _.mixin function

1
2
3
4
5
6
7
_.mixin({
partialSome: heroin
});
var addB = _.partialSome(add, {
a: 1,
c: 3
});

Using this approach leaves me free to refactor the argument order in add function, since it will no longer break the partial argument binding. I could even bind all arguments:

1
2
3
4
5
6
var sum = _.partialSome(add, {
a: 10,
b: 20,
c: 30
});
sum(); // 60

By reference

heroin only creates a link between the argument name and the object. Since objects in JavaScript are passed by reference, you can change the value of the injected parameter at any time

1
2
3
4
5
6
7
8
9
var args = {
a: 10,
b: 20,
c: 30
};
var sum = _.partialSome(add, args);
sum(); // 60
args.a = 0;
sum(); // 50

This is very flexible but can lead to the unexpected results. If you want to prevent accidental changes to the arguments, you can either clone or freeze the top level object

1
2
3
4
5
6
7
8
9
10
var args = {
a: 10,
b: 20,
c: 30
};
var sum = _.partialSome(add, Object.freeze(args));
// or
var sum = _.partialSome(add, _.clone(args));
// or
var sum = _.partialSome(add, _.cloneDeep(args));

Valid input functions

All partial binding functions expect a function with valid arity (number of arguments) to work correctly. For example, _.partialRight cannot work if the input function does not list its arguments

1
2
3
4
5
var add = function() {
var numbers = _.toArray(arguments);
...
}
_.partialRight(add, 1); // fails!

Only functions with valid .length value can be used.

Returned function

Because the returned proxy function is constructed dynamically using arguments array, it does not list its arguments, and thus cannot be used as input to the partial binding itself!

1
2
3
var addC = _.partialRight(add, 1); // bind c, leave a and b
addC.length; // 0
vadd addBC = _.partialRight(addC, 2); // fails!

One could get around this by compiling the proxy function from source text, but I think this would be terribly non optimizeable

1
2
// inside _.partialRight implementation
return new Function('function (a, b) { ... ');