Problem
You want to create a generic function (using ES2015 code) that produces for a given function the partial applied function.
Ingredients
- 1 function
- 2 fat arrow functions
- rest parameters
- spread operator
Directions
-
Given: a generic function for creating partial functions (see recipe 9).
function partial(fn /*, args...*/) { var args = Array.prototype.slice.call(arguments, 1); return function() { return fn.apply(this, args.concat(slice.call(arguments, 0))); }; } -
Use rest parameters to avoid arguments to array conversion.
function partial(fn, ...args) { return function(...argsInner) { return fn.apply(this, args.concat(argsInner)); }; } -
Replace the inner function with a fat arrow function.
function partial(fn, ...args) { return (...argsInner) => { return fn.apply(this, args.concat(argsInner)); }; } - Call the passed function
fndirectly.
```javascript
function partial(fn, ...args) {
return (...argsInner) => {
return fn.apply(this, args.concat(argsInner));
};
}
```- Use the spread operator in favour of the array concatenation.
```javascript
function partial(fn, ...args) {
return (...argsInner) => {
return fn(...args, ...argsInner);
};
}
```- … and remove the
returnfrom the inner function.
```javascript
function partial(fn, ...args) {
return (...argsInner) => fn(...args, ...argsInner);
}
```-
Make the outer function a fat arrow function as well.
const partial = (fn, ...args) => (...argsInner) => fn(...args, ...argsInner); -
Voilá, there you got a delicious generic function that creates partial applicable functions.
const partial = (fn, ...args) => (...argsInner) => fn(...args, ...argsInner); function createPerson(firstName, lastName) { return { firstName: firstName, lastName: lastName } } let createPersonWithFirstNameJohn = partial(createPerson, 'John'); let johnDoe = createPersonWithFirstNameJohn('Doe'); console.log(johnDoe.firstName); // "John" console.log(johnDoe.lastName); // "Doe" // And another example: const sum = (x, y, z) => x + y + z; const add5 = partial(sum, 5); const add5and6 = partial(sum, 5, 6); console.log(add5(6,7)); // 18 console.log(add5and6(7)); // 18Notes
-
Of course, if you need the runtime context of the function that is created, you cannot use fat arrow functions.
For example in the following code an object
personFactorywas created, which now contains thecreatePerson()function as object method. InsidecreatePerson()we access thedefaultAgemember via the keywordthis, which at this moment refers to the global object (e.g., thewindowobject in a browser based runtime environment).Therefore, when setting
this.defaultAgeas a value to the (new) propertyageof the object created increatePerson(), it gets the valueundefined(or in case the global object has a propertydefaultValueit gets that value).Bottom line: in every case where you need to have access to the current context at runtime, don’t use fat arrow functions for partial applications (but anyway: in real functional programming functions should not access the context at all).
const partial = (fn, ...args) => (...argsInner) => fn(...args, ...argsInner); let personFactory = { defaultAge: 55, createPerson: function(firstName, lastName) { return { firstName: firstName, lastName: lastName, age: this.defaultAge } } } let createPersonWithFirstNameJohn = partial(personFactory.createPerson, 'John'); let johnDoe = createPersonWithFirstNameJohn('Doe'); console.log(johnDoe.firstName); // "John" console.log(johnDoe.lastName); // "Doe" console.log(johnDoe.age); // undefined