Extracting data from a function chain without arrays
Asked Answered
T

5

4

This is an advanced topic of

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

I am pretty sure we can somehow extract data from a function chain without using an array storing data. The basic structure is :

L = a => L

very simple, but this structure generates a list:

L(1)(2)(3)(4)(5)(6)()

This may be related to What is a DList? , but this structure strictly depends on function chain only.

So, what is the way to pull out the whole values? Current achievement of mine merely pulling out the head and tail, and I don't know how to fix this.

EDIT: I forgot to mention what I try to do is

List.fold(f) / reduce(f)

operation.

So, if one choses f as Array.concat which means you can extract data as an array, but simply fold is not limited to array concatenation. and f can be add/sum etc.

So, currently, so far, to visualize the internal behavior, in a sense, I write log as f.

EDIT2

I must clarify more. The specification can be presented:

const f = (a) => (b) => a + b;//binary operation

A(a)(b)(f) = f(a)(b)  // a + b

A(a)(b)(c)(f) = f(f(a)(b))(c) // a + b + c

So this is exactly

(a b c).reduce(f)

thing, and when

f = (a) => (b) => a.concat(b)

The result would be [a, b, c].

Array.concat is merely a member of generalized binary operations f.

At first this challenge is easy for my skill, but turned out hard and felt it's better to ask smarter coder.

Thanks.

const A = a => {

    const B = b => (b === undefined)
        ? (() => {
            log("a " + a);
            return A();
        })()
        : c => (c === undefined)
            ? (() => {
                log("b " + b);
                return B()();
            })()
            : B;

    return B;

};
 

A(1)(2)(3)(4)(5)(6)()

function log(m)  {
    console.log((m)); //IO
    return m;
};

result:

b 6
a 1
a undefined
Topple answered 19/7, 2018 at 8:48 Comment(8)
Anyway, my concern is how you return such data if it's not in a array-like data structure?Galliot
This feels very abstract and pointless. What's the actual application here?Ow
About my previous question: I see how iterator protocol may assist on this if you don't want an array, but at least, you need a sequence, am I mistaken?Galliot
@MatíasFidemraizer Thanks a lot for your contribution again. Sorry I really forgot to mention the important thing. This is to implement fold of list. Generalized. I have edit my question, too/Topple
Given A(a)(b)(f) how would you know whether f is a value to be added to the list or a reducing function?Protoactinium
@bayesian-study Uhm I see. BTW it could be implemented with closures as the answered has already pointed out.Galliot
@MatíasFidemraizer Thanks again! I post my own answer that is work in progress. Please review it, too. In this case, for simplicity, f is just distinguished by type of operationTopple
@AaditMShah Yeah, hello thanks again! as I mentioned above, in this case, simply, to distinguish by type of "function"Topple
P
1

Given an expression like A(a)(b)(f) where f is a function, it's impossible to know whether f is supposed to be added to the list or whether it's the reducing function. Hence, I'm going to describe how to write expressions like A(a)(b)(f, x) which is equivalent to [a, b].reduce(f, x). This allows us to distinguish when the list ends depending upon how many arguments you provide:

const L = g => function (x, a) {
    switch (arguments.length) {
    case 1: return L(k => g((f, a) => k(f, f(a, x))));
    case 2: return g((f, a) => a)(x, a);
    }
};

const A = L(x => x);

const xs = A(1)(2)(3)(4)(5);

console.log(xs((x, y) => x + y, 0));        // 15
console.log(xs((x, y) => x * y, 1));        // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

It works due to continuations. Every time we add a new element, we accumulate a CPS function. Each CPS function calls the previous CPS function, thereby creating a CPS function chain. When we give this CPS function chain a base function, it unrolls the chain and allows us to reduce it. It's the same idea behind transducers and lenses.


Edit: user633183's solution is brilliant. It uses the Church encoding of lists using right folds to alleviate the need for continuations, resulting in simpler code which is easy to understand. Here's her solution, modified to make foldr seem like foldl:

const L = g => function (x, a) {
    switch (arguments.length) {
    case 1: return L((f, a) => f(g(f, a), x));
    case 2: return g(x, a);
    }
};

const A = L((f, a) => a);

const xs = A(1)(2)(3)(4)(5);

console.log(xs((x, y) => x + y, 0));        // 15
console.log(xs((x, y) => x * y, 1));        // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

Here g is the Church encoded list accumulated so far. Initially, it's the empty list. Calling g folds it from the right. However, we also build the list from the right. Hence, it seems like we're building the list and folding it from the left because of the way we write it.


If all these functions are confusing you, what user633183 is really doing is:

const L = g => function (x, a) {
    switch (arguments.length) {
    case 1: return L([x].concat(g));
    case 2: return g.reduceRight(x, a);
    }
};

const A = L([]);

const xs = A(1)(2)(3)(4)(5);

console.log(xs((x, y) => x + y, 0));        // 15
console.log(xs((x, y) => x * y, 1));        // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

As you can see, she is building the list backwards and then using reduceRight to fold the backwards list backwards. Hence, it looks like you're building and folding the list forwards.

Protoactinium answered 19/7, 2018 at 10:57 Comment(6)
This is truly awesome! honestly, in this moment, I don't understand the concept behind this, but I am grateful to what you have shown me today. I love your attitude to programming and people. Very inspiring!Topple
Could you please give me a version of a single parameter with the initial element as the default value?? I tried by myself, but I could not make it.Topple
so far, const isFunction = f => (typeof f === 'function') would be fine to distinguish the evaluation trigger. Thanks a lot in advance!Topple
I post another Question #51428297 please review it when you have time!Topple
Based on this jsPerf jsperf.com/l-vs-array/1, I believe that this is an experiment in terms of optimization. I don't know about the perf in a functional language which might include optimizationsGalliot
#51486431 This is a new topic related to this topic. Please review, thanks!Topple
M
4

Quite the series of questions you have here. Here's my take on it:

We start with a way to construct lists

  • nil is a constant which represents the empty list
  • cons (x, list) constructs a new list with x added to the front of list

// nil : List a
const nil =
  (c, n) => n

// cons : (a, List a) -> List a
const cons = (x, y = nil) =>
  (c, n) => c (y (c, n), x)

// list : List Number
const myList = 
  cons (1, cons (2, cons (3, cons (4, nil))))

console.log (myList ((x, y) => x + y, 0))
// 10

And to satisfy your golfy variadic curried interface, here is autoCons

const autoCons = (init, n) => 
{ const loop = acc => (x, n) =>
    isFunction (x)
      ? acc (x, n)
      : loop (cons (x, acc))
  return loop (nil) (init, n)
}

const isFunction = f =>
  f != null && f.constructor === Function && f.length === 2

const nil =
  (c, n) => n

const cons = (x, y = nil) =>
  (c, n) => c (y (c, n), x)

console.log
  ( autoCons (1) ((x,y) => x + y, 0)             // 1
  , autoCons (1) (2) ((x,y) => x + y, 0)         // 3
  , autoCons (1) (2) (3) ((x,y) => x + y, 0)     // 6
  , autoCons (1) (2) (3) (4) ((x,y) => x + y, 0) // 10
  )

Our encoding makes it possible to write other generic list functions, like isNil

// isNil : List a -> Bool
const isNil = l =>
  l ((acc, _) => false, true)

console.log
  ( isNil (autoCons (1))     // false
  , isNil (autoCons (1) (2)) // false
  , isNil (nil)              // true
  )

Or like length

// length : List a -> Int
const length = l =>
  l ((acc, _) => acc + 1, 0)

console.log
  ( length (nil)                  // 0
  , length (autoCons (1))         // 1
  , length (autoCons (1) (2))     // 2
  , length (autoCons (1) (2) (3)) // 3
  )

Or nth which fetches the nth item in the list

// nth : Int -> List a -> a
const nth = n => l =>
  l ( ([ i, res ], x) =>
        i === n
          ? [ i + 1, x ]
          : [ i + 1, res]
    , [ 0, undefined ]
    ) [1]

console.log
  ( nth (0) (autoCons ("A") ("B") ("C")) // "A"
  , nth (1) (autoCons ("A") ("B") ("C")) // "B"
  , nth (2) (autoCons ("A") ("B") ("C")) // "C"
  , nth (3) (autoCons ("A") ("B") ("C")) // undefined
  )

We can implement functions like map and filter for our list

// map : (a -> b) -> List a -> List b
const map = f => l =>
  l ( (acc, x) => cons (f (x), acc)
    , nil
    )

// filter : (a -> Bool) -> List a -> List a
const filter = f => l =>
  l ( (acc, x) => f (x) ? cons (x, acc) : acc
    , nil
    )

We can even make a program using our list which takes a list as an argument

// rcomp : (a -> b) -> (b -> c) -> a -> c
const rcomp = (f, g) =>
  x => g (f (x))

// main : List String -> String   
const main = letters =>
  autoCons (map (x => x + x))
           (filter (x => x !== "dd"))
           (map (x => x.toUpperCase()))
           (rcomp, x => x)
           (letters)
           ((x, y) => x + y, "")

main (autoCons ("a") ("b") ("c") ("d") ("e"))
// AABBCCEE

Run the program in your browser below

const nil =
  (c, n) => n

const cons = (x, y = nil) =>
  (c, n) => c (y (c, n), x)

const isFunction = f =>
  f != null && f.constructor === Function && f.length === 2

const autoCons = (init, n) => 
{ const loop = acc => (x, n) =>
    isFunction (x)
      ? acc (x, n)
      : loop (cons (x, acc))
  return loop (nil) (init, n)
}

const map = f => l =>
  l ( (acc, x) => cons (f (x), acc)
    , nil
    )

const filter = f => l =>
  l ( (acc, x) => f (x) ? cons (x, acc) : acc
    , nil
    )

const rcomp = (f, g) =>
  x => g (f (x))

const main = letters =>
  autoCons (map (x => x + x))
           (filter (x => x !== "dd"))
           (map (x => x.toUpperCase()))
           (rcomp, x => x)
           (letters)
           ((x, y) => x + y, "")

console.log (main (autoCons ("a") ("b") ("c") ("d") ("e")))
// AABBCCEE

Sorry, my bad

Let's rewind and look at our initial List example

// list : List Number
const myList = 
  cons (1, cons (2, cons (3, cons (4, nil))))

console.log
  ( myList ((x, y) => x + y, 0) // 10
  )

We conceptualize myList as a list of numbers, but we contradict ourselves by calling myList (...) like a function.

This is my fault. In trying to simplify the example, I crossed the barrier of abstraction. Let's look at the types of nil and cons

// nil : List a
// cons : (a, List a) -> List a

Given a list of type List a, how do we get a value of type a out? In the example above (repeated below) we cheat by calling myList as a function. This is internal knowledge that only the implementer of nil and cons should know

// myList is a list, not a function... this is confusing...
console.log
  ( myList ((x, y) => x + y, 0) // 10
  )

If you look back at our original implementation of List,

// nil : List a
const nil =
  (c, n) => n

// cons : (a, List a) -> List a
const cons = (x, y = nil) =>
  (c, n) => c (y (c, n), x)

I also cheated you giving simplified type annotations like List a. What is List, exactly?

We're going to address all of this and it starts with our implementation of List

List, take 2

Below nil and cons have the exact same implementation. I've only fixed the type annotations. Most importantly, I added reduce which provides a way to get values "out" of our list container.

The type annotation for List is updated to List a r – this can be understood as "a list containing values of type a that when reduced, will produce a value of type r."

// type List a r = (r, a) -> r

// nil : List a r
const nil =
  (c, n) => n

// cons : (a, List a r) -> List a r
const cons = (x, y = nil) =>
  (c, n) => c (y (c, n), x)

// reduce : ((r, a) -> r, r) -> List a -> r
const reduce = (f, init) => l =>
  l (f, init)

Now we can maintain List as a sane type, and push all the wonky behavior you want into the autoCons function. Below we update autoCons to work with our list acc using our new reduce function

const autoCons = (init, n) => 
{ const loop = acc => (x, n) =>
    isFunction (x)
      // don't break the abstraction barrier
      ? acc (x, n)
      // extract the value using our appropriate list module function
      ? reduce (x, n) (acc)
      : loop (cons (x, acc))
  return loop (nil) (init, n)
}

So speaking of types, let's examine the type of autoCons

autoCons (1)                  // "lambda (x,n) => isFunction (x) ...
autoCons (1) (2)              // "lambda (x,n) => isFunction (x) ...
autoCons (1) (2) (3)          // "lambda (x,n) => isFunction (x) ...
autoCons (1) (2) (3) (add, 0) // 6

Well autoCons always returns a lambda, but that lambda has a type that we cannot determine – sometimes it returns another lambda of its same kind, other times it returns a completely different result; in this case some number, 6

Because of this, we cannot easily mix and combine autoCons expressions with other parts of our program. If you drop this perverse drive to create variadic curried interfaces, you can make an autoCons that is type-able

// autoCons : (...a) -> List a r
const autoCons = (...xs) =>
{ const loop = (acc, x = nil, ...xs) =>
    x === nil
      ? acc
      : loop (cons (x, acc), ...xs)
  return loop (nil, ...xs)
}

Because autoCons now returns a known List (instead of the mystery unknown type caused by variadic currying), we can plug an autoCons list into the various other functions provided by our List module.

const c =
  autoCons (1, 2, 3)

const d =
  autoCons (4, 5, 6)

console.log
  ( toArray (c)                         // [ 1, 2, 3 ]
  , toArray (map (x => x * x) (d))      // [ 16, 25, 36 ]
  , toArray (filter (x => x != 5) (d))  // [ 4, 6 ]
  , toArray (append (c, d))             // [ 1, 2, 3, 4, 5, 6 ]
  )

These kind of mix-and-combine expressions is not possible when autoCons returns a type we cannot rely upon. Another important thing to notice is the List module gives us a place to expand its functionality. We can easily add functions used above like map, filter, append, and toArray – you lose this flexibility when trying to shove everything through the variadic curried interface

Let's look at those additions to the List module now – as you can see, each function is well-typed and has behavior we can rely upon

// type List a r = (r, a) -> r

// nil : List a r
// cons : (a, List a r) -> List a r
// reduce : ((r, a) -> r, r) -> List a r -> r

// length : List a r -> Int
const length =
  reduce
    ( (acc, _) => acc + 1
    , 0
    )

// map : (a -> b) -> List a r -> List b r
const map = f =>
  reduce
    ( (acc, x) => cons (f (x), acc)
    , nil
    )

// filter : (a -> Bool) -> List a r -> List a r
const filter = f =>
  reduce
    ( (acc,x) =>  f (x) ? cons (x, acc) : acc
    , nil
    )

// append : (List a r, List a r) -> List a r
const append = (l1, l2) =>
  (c, n) =>
    l2 (c, l1 (c, n))

// toArray : List a r -> Array a
const toArray =
  reduce
    ( (acc, x) => [ ...acc, x ]
    , []
    )

Even autoCons makes sense as part of our module now

// autoCons : (...a) -> List a r
const autoCons = (...xs) =>
{ const loop = (acc, x = nil, ...xs) =>
    x === nil
      ? acc
      : loop (cons (x, acc), ...xs)
  return loop (nil, ...xs)
}

Add any other functions to the List module

// nth: Int -> List a r -> a
// isNil : List a r -> Bool
// first : List a r -> a
// rest : List a r -> List a r
// ...
Metatherian answered 21/7, 2018 at 22:41 Comment(13)
That's a brilliant solution. Build the list in reverse and then use foldr so that it seems like you're building the list forwards and using foldl. I'd only change one thing, swap the arguments of c in cons (i.e. const cons = (x, y) => (c, n) => c(y(c, n), x)). Hence, you take the accumulator first and the current value second (just as foldl specifies). Doing so would change the output of your last example to "AABBCCEE" which is actually what I first expected the output to be until I understood what your code was doing. It also allows you to easily convert your list into an array.Protoactinium
JavaScript Code Guru again! I think you technically invented a new language inside vanilla JS, and this formalization should be shared widely somehow. Amazing. Thank you always!Topple
I agree with @AaditMShah , the traditional way to construct list as explained in lisp/Scheme textbook is very wrong. Nil should not be in the tail of the list, instead it should be in the head. Lisp/Scheme brought so much confusion having twisted list structure(0 =nil in the tail) to programming world.Topple
@bayesian-study What? No, that's not what I said. The textbook way of defining lists is absolutely correct. Plus, nil can never be the head of the list because of the algebraic structure of the list data type. I just said that building the list backwards and using foldr to make it seem like you're building the list forwards and using foldl is a smart solution.Protoactinium
@AaditMShah Thanks for the response. I misunderstand what you meant, and I still believe LIsp/Scheme list implementation is very wrong. It should be ((((0=ID),a),b),c). Nil on the tail is mess, especially for list of list, you need multiple Nil, but in ((((0=ID),a),b),c), you need only one Nil even for list for list.Topple
@bayesian-study In your example, 0=ID is still the tail and c is the head. It's just that you've written it backwards. Normally, we write lists like cons(1, cons(2, cons(3, nil))). What you're doing cons(cons(cons(nil, 3), 2), 1). It's the exact same data structure except that the arguments of cons have been flipped. Hence we write it backwards. Now, your A(1)(2)(3) structure is the same as cons(cons(cons(nil, 1), 2), 3). Hence, it's actually equivalent to [3,2,1]. However, [3,2,1].reduceRight(f, x) is the same as [1,2,3].reduce(f, x). Therefore, foldr seems like foldl.Protoactinium
@AaditMShah I disagree. Here's a reference what I'm talking about spacetimeprogramminglanguage.github.io/contents/entries/entry0/… and this is completely opposite list structure of Lisp/Scheme and not replaceable by cons.Topple
@bayesian-study For some reason, I think KenOKABE and you are either friends in real life or the same person. Anyway, the example you provided is still a backwards list. The head of { { { {} 5 } 2 } 7 } is 7 and not {} because 7 is the outermost value. It's just that you're writing the list backwards. It's not the "complete opposite list structure". In fact, it's the exact same list structure. It's just that you've flipped the arguments. Instead of taking the head first, you're taking the tail first.Protoactinium
@AaditMShah I still can't aagree. Your perspective of the head of { { { {} 5 } 2 } 7 } is 7 and not {}" simply depends on how you evaluate the list. If you chose to evaluate the outermost value is the first member of the list, then you can conclude "because 7 is the outermost value", but the fact is there is inverse way to evaluate.Topple
@AaditMShah thanks for your comment. I switched the argument order per your recommendation and the result does feel more natural, especially in JS.Metatherian
@user633183 #51486431 This is a new topic related to this topic. Please review, thanks!Topple
@bayesian-study I added an update to my post in hopes to help you understand the flaw in variadic curryingMetatherian
@user633183 Thank you very much for your educational update! I'm studying every sentence now, and so far, I'd like to propose something. "Well autoCons always returns a lambda, but that lambda has a type that we cannot determine – sometimes it returns another lambda of its same kind, other times it returns a completely different result; in this case some number, 6" makes sense,and I just think that instead returning number 6, returning (6) would not cause the type-mismatch problem. Since now we have toArray method, what is the difference?Topple
U
1

I'll have to admit I haven't read through your linked questions and I'm mainly here for the fun puzzle... but does this help in any way?

I figured you want to differentiate between adding an element (calling with a new value) and running a function on the list (calling with a function). Since I had to somehow pass the function to run, I couldn't get the (1) vs () syntax to work.

This uses an interface that returns an object with concat to extend the list, and fold to run a reducer on the list. Again, not sure if it's a complete answer, but it might help you explore other directions.

const Empty = Symbol();

const L = (x, y = Empty) => ({
  concat: z => L(z, L(x, y)),
  fold: (f, seed) => f(x, y === Empty ? seed : y.fold(f, seed))
});

const sum = (a, b) => a + b;


console.log(
  L(1)
    .concat(2).concat(3).concat(4).concat(5).concat(6)
    .fold(sum, 0)
)
Untwist answered 19/7, 2018 at 9:28 Comment(1)
My appreciations. Based on your code, I try to refactor to fit the presented format, and post my own answer: Work in progress. I think this is almost done.Topple
P
1

Given an expression like A(a)(b)(f) where f is a function, it's impossible to know whether f is supposed to be added to the list or whether it's the reducing function. Hence, I'm going to describe how to write expressions like A(a)(b)(f, x) which is equivalent to [a, b].reduce(f, x). This allows us to distinguish when the list ends depending upon how many arguments you provide:

const L = g => function (x, a) {
    switch (arguments.length) {
    case 1: return L(k => g((f, a) => k(f, f(a, x))));
    case 2: return g((f, a) => a)(x, a);
    }
};

const A = L(x => x);

const xs = A(1)(2)(3)(4)(5);

console.log(xs((x, y) => x + y, 0));        // 15
console.log(xs((x, y) => x * y, 1));        // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

It works due to continuations. Every time we add a new element, we accumulate a CPS function. Each CPS function calls the previous CPS function, thereby creating a CPS function chain. When we give this CPS function chain a base function, it unrolls the chain and allows us to reduce it. It's the same idea behind transducers and lenses.


Edit: user633183's solution is brilliant. It uses the Church encoding of lists using right folds to alleviate the need for continuations, resulting in simpler code which is easy to understand. Here's her solution, modified to make foldr seem like foldl:

const L = g => function (x, a) {
    switch (arguments.length) {
    case 1: return L((f, a) => f(g(f, a), x));
    case 2: return g(x, a);
    }
};

const A = L((f, a) => a);

const xs = A(1)(2)(3)(4)(5);

console.log(xs((x, y) => x + y, 0));        // 15
console.log(xs((x, y) => x * y, 1));        // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

Here g is the Church encoded list accumulated so far. Initially, it's the empty list. Calling g folds it from the right. However, we also build the list from the right. Hence, it seems like we're building the list and folding it from the left because of the way we write it.


If all these functions are confusing you, what user633183 is really doing is:

const L = g => function (x, a) {
    switch (arguments.length) {
    case 1: return L([x].concat(g));
    case 2: return g.reduceRight(x, a);
    }
};

const A = L([]);

const xs = A(1)(2)(3)(4)(5);

console.log(xs((x, y) => x + y, 0));        // 15
console.log(xs((x, y) => x * y, 1));        // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

As you can see, she is building the list backwards and then using reduceRight to fold the backwards list backwards. Hence, it looks like you're building and folding the list forwards.

Protoactinium answered 19/7, 2018 at 10:57 Comment(6)
This is truly awesome! honestly, in this moment, I don't understand the concept behind this, but I am grateful to what you have shown me today. I love your attitude to programming and people. Very inspiring!Topple
Could you please give me a version of a single parameter with the initial element as the default value?? I tried by myself, but I could not make it.Topple
so far, const isFunction = f => (typeof f === 'function') would be fine to distinguish the evaluation trigger. Thanks a lot in advance!Topple
I post another Question #51428297 please review it when you have time!Topple
Based on this jsPerf jsperf.com/l-vs-array/1, I believe that this is an experiment in terms of optimization. I don't know about the perf in a functional language which might include optimizationsGalliot
#51486431 This is a new topic related to this topic. Please review, thanks!Topple
T
0

Work in progress

Thanks to the stunning contribution by @user3297291 , I somehow could refactor the code to fit my specification, but not working because I am lost the concept during the implementation :(

The point is whole thing must be curried, and no object.method is involved.

Can anyone "debug" please :)

The initial value is set to the first element, in this example as 1

I think this is almost done.

const isFunction = f => (typeof f === 'function');

const Empty = Symbol();

const L = (x = Empty) => (y = Empty) => z => isFunction(z)
    ? (() => {
        const fold = f => seed => f(x)(y) === Empty
            ? seed
            : (L)(y)(f);
        return fold(z)(x);
    })()
    : L(z)(L(x)(y));


const sum = a => b => a + b;


console.log(
    L(1)(2)(3)(4)(5)(6)(sum)
);

Output

 z => isFunction(z)
    ? (() => {
        const fold = f => seed => f(x)(y) === Empty
            ? seed
            : (L)(y)(f);
        return fold(z)(x);
    })()
    : L(z)(L(x)(y))
Topple answered 19/7, 2018 at 10:46 Comment(0)
F
0

I've gone through the various questions you have but I'm still not sure I entirely understand what you're looking for. On the off chance you're simply looking to represent a linked list, here is a "dumb" representation that does not use clever tricks like overloaded arguments or default parameter values:

const List = (() => {
  const nil = Symbol()

  // ADT
  const Nil = nil
  const Cons = x => xs => ({ x, xs })
  const match = ({ Nil, Cons }) => l => l === nil ? Nil : Cons(l.x)(l.xs)

  // Functor
  const map = f => match({
    Nil,
    Cons: x => xs => Cons(f(x))(map(f)(xs))
  })

  // Foldable
  const foldr = f => z => match({
    Nil: z,
    Cons: x => xs => f(x)(foldr(f)(z)(xs)) // danger of stack overflow!
                                           // https://wiki.haskell.org/Foldr_Foldl_Foldl%27
  })

  return { Nil, Cons, match, map, foldr }
})()

const { Nil, Cons, match, map, foldr } = List
const toArray = foldr(x => xs => [x, ...xs])([])

const l = Cons(1)(Cons(2)(Cons(3)(Nil)))

const l2 = map(x => x * 2)(l)
const l3 = map(x => x * 3)(l2)

const a = toArray(l3)
console.log(a) // => [6, 12, 18]
Ferryman answered 20/7, 2018 at 12:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.