Chain to native Javascript functions

TL; DR:

How can I connect to Javascript map () using my own function? How -

stuff.map(i => i.key).avg()

      

where avg () is my own function to calculate the average of the array returned by the map?


In moving away from objects and towards functional programming with pure functions, I lost the comfortable

return this;

      

which allows me to chain.

If I have

let stuff = [
  {id: 1, name: 'tuan', country: 'VN', age: 23},
  {id: 2, name: 'nhung', country: 'US', age: 25},
  ...

//my own filter to pass as a param to native filter()
var filt = x => j => j.country === x;

//my own reducer for an array that computes an average
let avg = (arr) => (arr.reduce((acc, i) => acc + i) / arr.length);

      

then

stuff.filter(filt('VN')).map(i => i.age)

      

will return something like

[23, 34, 45]

      

but

stuff.filter(filt('VN')).map(i => i.age).avg()

      

gives an error like

filter().map().avg() is not a function 

      

How can we write functions that relate to native ones?

+3


source to share


5 answers


Method chaining is incompatible with function composition. But instead of modifying built-in prototypes or falling back to subtyping, you can create a container type that allows you to compose pure functions in the context of a method chaining:

function Box(x) {
  return new.target ? (this.x = x, this) : new Box(x)
}

Box.prototype.fold = function fold(f) {return f(this.x)};
Box.prototype.map = function map(f) {return new Box(f(this.x))};
Box.prototype.toString = function toString() {return `Box(${this.x})`};

const id = x => x;

const stuff = [
  {id: 1, name: 'foo', country: 'VN', age: 23},
  {id: 2, name: 'bar', country: 'US', age: 25},
  {id: 2, name: 'bat', country: 'VN', age: 34},
  {id: 2, name: 'baz', country: 'VN', age: 45}
];

const filt = x => j => j.country === x;

const avg = (arr) => (arr.reduce((acc, i) => acc + i) / arr.length);

console.log(
  Box(stuff.filter(filt('VN')).map(i => i.age))
  .map(xs => avg(xs))
  .fold(id) // yields 34
);
      

Run codeHide result




Box

is a functor and you can put values ​​of any type in this container. With, map

you can apply functions to a value inside a functor and get a new functor with the converted value back. fold

behaves the same, except that it returns null.

You may have noticed that my example is a little verbose and I could spare me the mapping.

+4


source


This must be avg(stuff.filter(filt('VN')).map(i => i.age))

because you have defined a function avg

that expects arr

as an argument. You haven't extended the prototype Array

with a method avg

.



+1


source


create avg method on Array.prototype

Array.prototype.avg = function() {
    return this.reduce((a,b) => Number(a) + Number(b)) / this.length;
}
var array = [
    { id: 1, key:2 },
    { id: 2, key:3 },
    { id: 3, key:7 },
    { id: 4, key:6 },
    { id: 5, key:4 }
]
var avg = array.map(i => i.key).avg();
console.log(avg);
      

Run codeHide result


+1


source


The chaining isn't magic - you just call the method on the return value of the function. If the function doesn't support this method, you need to add it to the prototype.

This works because it map

returns, and array and arrays have a method join()

:

var a = [1, 2, 3, 4, 5]
a.map((i) => i *2 ).join(",")

      

But arrays don't have a method avg()

unless you add one so the chaining doesn't work.

0


source


Well, you have some options here for sure. There is no one right way to achieve what you want. I think your best option is to extend the JavaScript Array class.

class PizzaCollection extends Array {
   // .. collection specific methods here...
   avg() {
     // you can iterate on `this`
   }
}

      

.map, .filter, etc. all will return an instance of PizzaCollection.

Try it!

const j = new PizzaCollection(1, 2, 3)
const k = j.map((num) => num * num)
k instanceof PizzaCollection // returns true
k.avg() // returns the avg

      

0


source







All Articles