partitura
v1.0.0
Published
Stand-alone implementation of _.partial. With several advantages
Downloads
9
Maintainers
Readme
Stand-alone implementation of _.partial. With several advantages.
The Need
If you are at least a bit familiar with functional programming in Javascript, you know about underscore.js.
If you are familiar with functional programming a bit more, you've definitely heard all this wails "Underscore is not True functional tool! A True functional approach is 'function-first, data-last' signature and currying! So I'll better use a {library_name} instead".
If you are not, here is an example.
It's really strange how can these people claim it "wrong", taking into account that it is specially designed, first of all, to provide an awesome chaining feature support. But ok, lets listen to them.
There are countless libraries, which "doing it right" (like this one), or which offer some tools to make your function to behave as you want.
What if I don't want (or, maybe, can not) to switch to a new library, to learn it's API, to check which of habitual to me methods are and aren't present there - but still want to enjoy a "True functional programming"? Well, this is what this plugin for.
A core idea
Underscore (and so, Lodash too) provides a partial
function, which has great feature - a placeholders. Using an Underscore object itself as placeholder is really great idea - it looks (and behaves!) just like sparse parameters in some "serious" languages.
So, to achieve a "function-first, data-last" signature we can do this:
var plus1 = function(value) { return value + 1; };
var add1 = _.partial(_.map, _, plus1);
add1([1, 2, 3]) // 2, 3, 4
Obviously, it's too verbose to be convenient. Mostly, due to explicit using of partial
. This is the problem a guys from previous paragraph are talking about. A much more convenient is just to write at least this:
var add1 = _.map(_, plus1);
And, actually, this is what this little plugin allows.
Yes, it's not a "True" auto-currying. It's not a currying at all - it's just a partial application. But I don't think that manually adding a little underscore symbol is so big deal. And we have a significant flexibility and control over parameters order instead (see below).
So let see how it works.
How it works
Plugin provides you an object with one main create
method. It creates a helper, providing an implementation of partial
function, which will use a given object as a placeholder.
If we are already using Underscore and have global _
object, it may look like this:
var partialize = require('partitura').create(_);
var myMap = partialize(_.map);
var add1 = myMap(_, plus1);
add1([1, 2, 3]); // [2, 3, 4]
That's it. Simple, isn't it?
A one big difference
In difference from from traditional currying approach, Partitura does not check given function's parameters count. You may already know what problems that approach causes - function can have variable parameters count, even 0 positional parameters at all - and then we need to invent something to create a curried function we really need. Here is good article about this.
Instead of this, our little, but brave helper just checks whether you've passed a placeholder to it. Simple, practical and logical - if you're passing a placeholder, you need a partial application; if you're not - you want to execute source function.
For example. map
accepts a function context as third parameter. Let's modify our plus1
function a bit and check how it works:
var plus1AndLogContext = function(value) {
console.log(this);
return value + 1;
};
var myMap = partialize(_.map);
var add1 = myMap(_, plus1AndLogContext);
add1([1, 2, 3]); // logs Window object 3 times, returns [2, 3, 4]
var add1WithCtx = add1([1, 2, 3], _);
console.log( typeof add1WithContext ) // "function"
add1WithCtx({ x: 42 }) // logs "{ x: 42 }" 3 times, returns [2, 3, 4]
As parameters count isn't checked, you can go crazy with placeholders:
add1([1,2,3], _, _)(_)(_, _)(_, _, _)(_)({ x: 42 }) // still returns [2, 3, 4]
I think there is no need to do something to deny to write such madness - as it still works after all.
Patching objects
Well, all this is ok, but remember what we've started from - using a "right library". Patching each function manually has absolutely no difference from origin _.partial(_.map, ...)
approach.
Here an all
helper comes. You can pass an object to it, and all it's methods will be turned in "partialized" versions:
var partialize = require('partitura').create(_);
partialize.all(_); // patch Underscore itself
var add1 = _.map(_, plus1);
Actually, exactly for this plugin was originally designed. But than I got carried away a bit)
Well, I was not fully honest when told that source function's parameters count isn't checked. all
helper processes only functions which have more than 1 parameter. It's done for optimization purposes. For example, Underscore has a lot of single-argument methods, and there is no need to wrap each of them, creating a lot of new functions and consuming additional memory. To handle edge case with functions with variable parameters count, there is a special force
option (see below).
Options
all
helper has some options to configure it's behavior:
force
:Array
- list here methods which should be wrapped disregarding their's parameter count. Use it for functions with variable parameters count.
Default: []
ownOnly
:Boolean
- whether to process only own properties of given object.
Default: true
saveAs
:String | undefined
- maybe, you don't want to replace origin methods in object. Don't know, why, but you can. In this case, specify this option, and partialized function will be stored in property of origin one.
partialize.all(_, { saveAs: 'use' });
var add1 = _.map.use(_, plus1);
Default: undefined
Adapters
Adapter is pre-created Partitura instance with pre-defined force
option - to deal with features of specific library.
For now only underscore
adapter is defined.
Syntax:
require('partitura').adapters.underscore.patch(_); // need to explicitly pass a lib object
A saveAs
option value can be passed as second parameter to patch
.