2

A simple function below:

const L = a => L;

forms

L
L(1)
L(1)(2)
...

This seems to form a list but the actual data is not stored at all, so if it's required to store the data such as [1,2], what is the smartest practice to have the task done?

const L = (a) => {
 // do somthing
  return L;
};

I would prefer this concise arrow function style, and do not want to destroy the outer structure as much as possible. Surely, I understand some outer structure modification is required, but I am curious what is possible especially in functional style not OO.

The specification is simply to store data of the function chain.

Any ideas? Thanks.

An initial simplest approach would be:

const L = (a) => {
  L.val = a;
  return L;
};
L.val = L;

can do some, but no data accumulation.

{ [Function: L] val: [Circular] }
{ [Function: L] val: 1 }
{ [Function: L] val: 2 }

Notice:

Every list should be independent for the accumulation.

L(3)(4)

will return [3,4] not [2,3,3,4] with prior accumulation of another lists.

Advanced topic!

How to store data of a functional chain of Monoidal List?

  • Can you clarify? So you want a function which will return a list, basically? So `x = L(1)(2)(3)` will be [1, 2, 3] ? – John Go-Soco Jul 11 '18 at 08:05
  • You pointed to monoids, which can do what you want, you should improve your question to explain what is the problem with the approach you [linked to in your comments to an answer](https://github.com/kenokabe/free-monoid/blob/master/test.js). – Ruan Mendes Jul 11 '18 at 08:22
  • It turns out to be Monoids, but not limited to, because it can be Magma tree structure to store data. –  Jul 11 '18 at 08:27

4 Answers4

5

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
  )
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 3
    This is truly one of the most brilliant response I've ever seen in S.O. There are so many things to learn from your post, and actually I will gratefully study and play. In fact, this topic has been hazy to me for a while, but I could manage in some way that I feel not optimized at all. My sincere appreciation –  Jul 11 '18 at 15:01
  • 1
    @bayesian-study most of my answers go into this level of detail or significantly more. If you're studying functional techniques I think you will enjoy them :D – Mulan Jul 11 '18 at 15:24
  • Yeah, so far, for this topic, at first sight of `L (1) (2) () // [ 1, 2 ]`, I thought, "ok, that's not exactly what I need", but on second thought, it's simply lazy-evaluation that I prefer and potential problem solver for my other issues. This is not what I expected, but really happy to have answers like this. Thanks again. –  Jul 11 '18 at 16:48
  • 1
    @bayesian-study what do you need exactly? I like [wishful thinking](https://stackoverflow.com/a/49603756/633183) as a general technique and demonstrate it [here](https://stackoverflow.com/a/48921903/633183) and [here](https://stackoverflow.com/a/46918344/633183). – Mulan Jul 11 '18 at 17:35
  • https://stackoverflow.com/questions/51297054/how-to-store-data-of-a-functional-chain-of-monoidal-list Here is my need exactly. I created an advanced topic for this. @user633183 –  Jul 12 '18 at 04:08
  • 1
    Thanks for the answer. Simple and elegant! `const L = (...values) => values` – John Go-Soco Jul 12 '18 at 09:32
  • @bayesian-study I [added an answer](https://stackoverflow.com/a/51308605/633183) to your new question. I hope it helps :D – Mulan Jul 12 '18 at 15:06
2

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.

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • Thanks a lot for your contribution for this topic! Your approach is very similar and currying user633183's function, but I think your function is rather more concise and easy to grasp the concept. I like better. Sorry I could not mark your answer as validated. His answer is earlier and gave me the concept background, but thanks again! –  Jul 12 '18 at 00:25
  • please check out my advance topic related to this one: https://stackoverflow.com/questions/51297054/how-to-store-data-of-a-functional-chain-of-monoidal-list –  Jul 12 '18 at 04:24
  • You gotta love JavaScript! – Adelin Jul 13 '18 at 07:33
  • @Adelin VanillaJS rocks :D – Matías Fidemraizer Jul 13 '18 at 08:16
  • @bayesian-study check my updated answer: I've provided you another approach :D – Matías Fidemraizer Jul 13 '18 at 10:28
  • @Adelin Thanks for your contribution again. I'm studying a lot in this week! –  Jul 13 '18 at 10:49
  • @MatíasFidemraizer Sure! Since every monad js also monoid, we can jump there and reduct in the way exactly like you performs. I will study. Thanks! –  Jul 13 '18 at 10:51
  • @bayesian-study Yup! BTW my implementation isn't yet a *monad*. But take it as a sample. Fill the gaps :D – Matías Fidemraizer Jul 13 '18 at 10:53
  • @bayesian-study Actually it seems like `Writer` is the way to go and the definitive answer to your global question. In fact, `Writer` is used to implement logging and some other things. – Matías Fidemraizer Jul 13 '18 at 10:58
  • @bayesian-study I mean that it's not just to create lists and be able to read them at some point. It's an approach to accumulate computational results across a function composition. – Matías Fidemraizer Jul 13 '18 at 10:59
  • @MatíasFidemraizer Thanks. For the advanced topic Question. I've just marked as the accepted answer to https://stackoverflow.com/a/51300750/6440264 The code is the most complete for left-right identity and associative and lazy-eval. –  Jul 13 '18 at 11:03
1

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.

0

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!

John Go-Soco
  • 886
  • 1
  • 9
  • 20
  • 1
    Thanks, but more than 3, (L)(L)(L)(L) breaks your code. This is hard implementation, it appears, really. –  Jul 11 '18 at 10:55