Neues Node.js-Buch
Alle Artikel

Create a generic function for partial application using arrow functions

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

  1. 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)));
      };
    }
  2. Use rest parameters to avoid arguments to array conversion.

    function partial(fn, ...args) {
      return function(...argsInner) {
        return fn.apply(this, args.concat(argsInner));
      };
    }
  3. Replace the inner function with a fat arrow function.

    function partial(fn, ...args) {
      return (...argsInner) => {
        return fn.apply(this, args.concat(argsInner));
      };
    }
  4. Call the passed function fn directly.
```javascript
function partial(fn, ...args) {
  return (...argsInner) => {
    return fn.apply(this, args.concat(argsInner));
  };
}
```
  1. Use the spread operator in favour of the array concatenation.
```javascript
function partial(fn, ...args) {
  return (...argsInner) => {
    return fn(...args, ...argsInner);
  };
}
```
  1. … and remove the return from the inner function.
```javascript
function partial(fn, ...args) {
  return (...argsInner) => fn(...args, ...argsInner);
}
```
  1. Make the outer function a fat arrow function as well.

    const partial = (fn, ...args) => (...argsInner) => fn(...args, ...argsInner);
  2. 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

  3. 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 the createPerson() function as object method. Inside createPerson() we access the defaultAge member via the keyword this, which at this moment refers to the global object (e.g., the window object in a browser based runtime environment).

    Therefore, when setting this.defaultAge as a value to the (new) property age of the object created in createPerson(), it gets the value undefined (or in case the global object has a property defaultValue 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

Alternative recipes