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
fn
directly.
```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
return
from 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)); // 18
Notes
-
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
personFactory
was created, which now contains thecreatePerson()
function as object method. InsidecreatePerson()
we access thedefaultAge
member via the keywordthis
, which at this moment refers to the global object (e.g., thewindow
object in a browser based runtime environment).Therefore, when setting
this.defaultAge
as a value to the (new) propertyage
of the object created increatePerson()
, it gets the valueundefined
(or in case the global object has a propertydefaultValue
it 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