1

If I have an array someArray that I first want to do some operations on and then pass that result to a function arrfun that takes an array as an argument. Like the following

let arr = someArray.filter(foo).map(bar)
let result = arrfun(arr)

In the above scenario I would like to avoid having to assign an intermediary variable to be passed to arrfun. I would like to have something like this.

Object.prototype.pipe = function(f) {return f(this)}

let result = someArray.filter(foo).map(bar).pipe(arrfun)
  • In lieu of a .pipe() how would you solve this?
  • Would it be sensible to introduce that function to Object?
  • Is pipe the best name for such a function? chain? pass?

New example

const pc = options => options
  .join(' ')
  .match(/--[\w.]* [\w.]*/g)
  .map(s => s.slice(2).split(' '))
  .map(([key, value]) => ({key, value}))
  .map(nestObject)
  .reduce((acc, val) => Object.assign(acc, val), {})

const nestObject = ({key, value}) => key
  .split('.')
  .reverse()
  .reduce((inner, key) => ({[key]: inner}), value)

In the above example a problem is that .match returns null if no match is found. Using .pipe you could solve it ny changing that line to

.pipe(s => s.match(/--[\w.]* [\w.]*/g) || [])

How would you solve this one without pipe?

antonbasic
  • 33
  • 7
  • What's wrong with just `arrfun(someArray.filter(foo).map(bar))`? – Bergi May 23 '17 at 09:04
  • If you really want to introduce a new method on `Object.prototype`, [do it properly](http://stackoverflow.com/q/13296340/1048572) at least (but no, [it's not sensible](https://stackoverflow.com/q/14034180/1048572)). – Bergi May 23 '17 at 09:06
  • [`chain`](https://github.com/fantasyland/fantasy-land#chain) means something different – Bergi May 23 '17 at 09:07
  • What's wrong with just `(options.join(…).match(…) || []).map(…).…`? – Bergi May 23 '17 at 09:08
  • Because, in my opinion, wrapping stuff like that doesn't scale in terms of readability. I think that already in this example the stuff that gets wrapped is to large and imagine having more wrappers. Just chaining stuff is much more simple. – antonbasic May 23 '17 at 10:25
  • One could also say that chaining stuff is *too* simple for complex use cases. There were good reasons for introducing variables and branching in JS syntax :-) – Bergi May 23 '17 at 11:19

2 Answers2

3

In the above scenario I would like to avoid having to assign an intermediary variable to be passed to arrfun.

Are you overlooking this simple, straightforward expression ?

let result = arrfun(someArray.filter(foo).map(bar))

right-to-left function composition

Or maybe you wish for classic (right-to-left) function composition?

const compose = (f,...fs) => x =>
  f === undefined ? x : f(compose(...fs)(x))

const filter = f => xs =>
  xs.filter(x => f(x))

const map = f => xs =>
  xs.map(x => f(x))

const foo = x =>
  x > 3
  
const bar = x =>
  x * x
  
const arrfun = xs =>
  xs.reverse()

const myfunc =
  compose(arrfun, map(bar), filter(foo))

let someArray = [1,2,3,4,5,6]

let result = myfunc(someArray)

console.log(result)
// [ 36, 25, 16 ]

left-to-right function composition

The same answer as above using left-to-right function composition

const compose = (f,...fs) => x =>
  f === undefined ? x : compose(...fs)(f(x))

const filter = f => xs =>
  xs.filter(x => f(x))

const map = f => xs =>
  xs.map(x => f(x))

const foo = x =>
  x > 3
  
const bar = x =>
  x * x
  
const arrfun = xs =>
  xs.reverse()

// notice order of functions
const myfunc =
  compose(filter(foo), map(bar), arrfun)
  
let someArray = [1,2,3,4,5,6]

let result = myfunc(someArray)

console.log(result)
// [ 36, 25, 16 ]

Identity functor

I dont think wrapping the entire thing scales in terms of readability. Imagine that you have to chain some more stuff to arrfun and then wrap that thing in yet another function.

You should see this answer I wrote about the Identity functor - This gives you a chainable interface but doesn't touch native prototypes

const Identity = x => ({
  runIdentity: x,
  map: f => Identity(f(x))
})

const foo = x =>
  x > 3
  
const bar = x =>
  x * x
  
const arrfun = xs =>
  xs.reverse()

const myfunc = xs =>
  Identity(xs)
    .map(xs => xs.filter(foo))
    .map(xs => xs.map(bar))
    .map(xs => arrfun(xs))
    .runIdentity

let someArray = [1,2,3,4,5,6]

let result = myfunc(someArray)

console.log(result)
// [ 35, 25, 16 ]

Of course if you keep filter and map as we defined before, it cleans up the definition of myfunc

const Identity = x => ({
  runIdentity: x,
  map: f => Identity(f(x))
})

const filter = f => xs =>
  xs.filter(x => f(x))

const map = f => xs =>
  xs.map(x => f(x))

const foo = x =>
  x > 3
  
const bar = x =>
  x * x
  
const arrfun = xs =>
  xs.reverse()

const myfunc = x =>
  Identity(x)
    .map(filter(foo))
    .map(map(bar))
    .map(arrfun)
    .runIdentity

let someArray = [1,2,3,4,5,6]

let result = myfunc(someArray)

console.log(result)
// [ 35, 25, 16 ]

And don't get hung up on foo and bar being defined up front. We can use lambda expressions directly within myfunc if you wanted to

const myfunc = xs =>
  Identity(xs)
    .map(xs => xs.filter(x => x > 3))
    .map(xs => xs.map(x => x * x))
    .map(arrfun)
    // or skip defining arrfun somewhere else and just ...
    // .map(xs => xs.reverse())
    .runIdentity
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • I dont think wrapping the entire thing scales in terms of readability. Imagine that you have to chain some more stuff to arrfun and then wrap that thing in yet another function. I might have overlooked composition though. Only thing that might be a disadvantage might be that I have to define functions first and then compose (might be a good thing as well). – antonbasic May 23 '17 at 08:38
  • I updated my answer to address your comment. The **Identity functor** is nice for making a chainable interface without touching native prototypes. Let me know if I can help you any further ^_^ – Mulan May 23 '17 at 08:43
  • Regarding your comment edit, `foo` and `bar` do not need to be predefined - they can be lambda expressions written directly in `myfunc` – Mulan May 23 '17 at 08:44
  • Very good examples, learned some nice stuff there, thnx. I added an extra example in the post that is a bit different. – antonbasic May 23 '17 at 09:08
0

You could compose your manipulator function and your result function first as reusable functions. Like:

let arr = [1,2,3,4,5];
const manipulatorFn = arr => arr.map(item => item +1);
const arrFunc = (arr, fn) => fn(arr);

or if you want to do some more stuff in the arrFunc

const arrFunc = (arr, fn) => {
  let a = fn(arr);

  // do some more stuff with a
  return a;
};

now you can fetch your result

let result = arrFunc(arr, manipulatorFn);
FrankCamara
  • 348
  • 4
  • 9