How to store data of a functional chain?

萝らか妹 提交于 2019-11-28 12:51:49

Function currying and variadic arguments don't really work together. It's a restriction made obvious once you realize that the following two expressions are incompatible

L (1)     -> [ 1 ]
L (1) (2) -> [ 1, 2 ]

Above L (1) returns a list, but in the second expression we expect L (1) to be a function that we can apply to 2. L (1) can be a list or it can be a function that produces a list; it cannot be both at the same time.

This is why others have proposed things like .list to get the actual value out. You can do that but know that using object properties or relying upon mutation is not necessary. You can use any signal of your choosing

const L = (x, acc = []) =>
  x === undefined
    ? acc
    : y => L (y, [...acc, x])
    
console.log
  ( L ()              // []
  , L (1) ()          // [ 1 ]
  , L (1) (2) ()      // [ 1, 2 ]
  , L (1) (2) (3) ()  // [ 1, 2, 3 ]
  )

We can abstract away the optional argument by using an auxiliary helper function. This technique is similar to the solution you found but here we avoid awkward assignment of values to function properties and instead use simple variables and non-mutating actions

const L = init =>
{ const loop = (acc, x) =>
    x === undefined
      ? acc
      : y => loop ([...acc, x], y)
  return loop ([], init)
}

console.log
  ( L ()              // []
  , L (1) ()          // [ 1 ]
  , L (1) (2) ()      // [ 1, 2 ]
  , L (1) (2) (3) ()  // [ 1, 2, 3 ]
  )

Or seeing as though your requirements are somewhat flexible, get creative with a more flexible encoding

const List = x =>
  k => k (x)
  
const append = x => xs =>
  List ([ ...xs, x ])

const prepend = x => xs =>
  List ([ x, ...xs ])
  
List ([]) (append (1)) (console.log)
// [ 1 ]

List ([ 2, 3 ]) (append (4)) (append (5)) (prepend (1)) (console.log)
// [ 1, 2, 3, 4, 5 ]

It's fun to push JavaScript's permissive syntaxes to their limits, but variadic functions are best defined using spread arguments

const L = (...values) =>
  values

console.log
  ( L ()         // []
  , L (1)        // [ 1 ]
  , L (1, 2)     // [ 1, 2 ]
  , L (1, 2, 3)  // [ 1, 2, 3 ]
  )

A less contrived example demonstrates a better use case

const max = (x, ...ys) =>
  ys.length === 0
    ? x
    : max2 (x, max (...ys))
    
const max2 = (x, y) =>
   x > y ? x : y
   
console.log
  ( max (1, 5, 3)     // 5
  , max (5, 2, 9, 7)  // 9
  , max (4)           // 4
  , max ()            // undefined
  )

Probably the following approach is someway crazy, but it works:

const L = x => {
    const L_ = xs => x => typeof x == 'undefined' ? xs : L_ ([...xs, x])
    
    return L_ ([]) (x)
}

const l1 = L (0) (2) (55) (383) (91) (6) ()
const l2 = L (22) (985) (82) (12) (1034) ()

console.log(l1)
console.log(l2)

Basically, the outer L receives the first element to add into the list. Subsequent calls will recurse inner L_ concatenating previous xs plus the new x until x evals falsy * undefined

Note how inner L_ is partially applied, so next call just requires a new x!

Yet another approach: Writer monad

There's another possible approach using Writer monad. Note that this sample implementation is simplified for the sake of the example:

/////
const Writer = acc => {
    return { acc }
}

const of = x => Writer ([x])
const map = f => m => Writer ([...m.acc, f (m.acc)])
const read = m => m.acc
/////

const pipe = xs => x => xs.reduce ((o, f) => f (o), x)
const K = x => () => x


const list = pipe ([ 
   of,
   map (K (1)), 
   map (K (38)), 
   map (K (1781)), 
   read
]) (123)

const list2 = pipe ([ 
   of,
   map (x => x * 2), 
   map (x => x + 4), 
   map (x => x * 10), 
   read
]) (123)

console.log ('#1 list:')
console.log (list)
console.log ('#2 list:')
console.log (list2)

* It shouldn't be falsy because 0 evals as false so L wouldn't support it as possible value.

Thanks to the kindness and skillfulness of @Adelin, I came up the ideal approach.

  const L = (a) => {
      const m = a => (m.list ? m.list : m.list = [])
          .push(a) && m;  //use `concat` and refactor needed instead of `push` that is not immutable
      return m(a); // Object construction!
    };

console.log(L);
console.log(L(2));
console.log(L(1)(2)(3))

some output:

{ [Function: m] list: [ 2 ] } 
{ [Function: m] list: [ 1, 2, 3 ] }

How elegant is this.

Again the credit of 95% of the contribution goes to @Adelin. My appreciation.

Here's the closest I can achieve.

const L = a => b => {
  if (!Array.isArray(a)) a = [a];
  a.push(b);
  return a;
}

x = L(L(L(3)(5))(5))(3)

console.log(x); // [3, 5, 5, 3]

I hope you like brackets!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!