This is an advanced topic of my prior question here:
How to store data of a functional chain?
The brief idea is
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?
One of the prominent ideas is from @user633183 which I marked as an accepted answer(see the Question link), and another version of the curried function is also provided by @Matías Fidemraizer .
So here goes:
const L = a => {
const m = list => x => !x
? list
: m([...list, x]);
return m([])(a);
};
const list1 = (L)(1)(2)(3); //lazy : no data evaluation here
const list2 = (L)(4)(5)(6);
console.log(list1()) // now evaluated by the tail ()
console.log(list2())
What I really like is it turns out lazy evaluation.
Although the given approach satisfies what I mentioned, this function has lost the outer structure or I must mentiion:
Algebraic structure
const L = a => L;
which forms list and more fundamentally gives us an algebraic structure of identity element, potentially along with Monoid or Magma.
Left an Right identity
One of the easiest examples of Monoids and identity is number and "Strings"
and [Array]
in JavaScript.
0 + a === a === a + 0
1 * a === a === a * 1
In Strings, the empty quoate ""
is the identity element.
"" + "Hello world" === "Hello world" === "Hello world" + ""
Same goes to [Array]
.
Same goes to L
:
(L)(a) === (a) === (a)(L)
const L = a => L;
const a = L(5); // number is wrapped or "lift" to Type:L
// Similarity of String and Array
// "5" [5]
//left identity
console.log(
(L)(a) === (a) //true
);
//right identity
console.log(
(a) === (a)(L) //true
);
and the obvious identity immutability:
const L = a => L;
console.log(
(L)(L) === (L) //true
);
console.log(
(L)(L)(L) === (L) //true
);
console.log(
(L)(L)(L)(L) === (L) //true
);
Also the below:
const L = a => L;
const a = (L)(1)(2)(3);
const b = (L)(1)(L)(2)(3)(L);
console.log(
(a) === (b) //true
);
Questions
What is the smartest or most elegant way (very functional and no mutations (no Array.push
, also)) to implement L
that satisfies 3 requirements:
Requirement 0 - Identity
A simple function:
const L = a => L;
already satisfies the identity law as we already have seen.
Requirement 1 - eval() method
Although L
satisfies the identity law, there is no method to access to the listed/accumulated data.
(Answers provided in my previous question provide the data accumulation ability, but breaks the Identity law.)
Lazy evaluation seems the correct approach, so providing a clearer specification:
provide eval
method of L
const L = a => L; // needs to enhance to satisfy the requirements
const a = (L)(1)(2)(3);
const b = (L)(1)(L)(2)(3)(L);
console.log(
(a) === (b) //true
);
console.log(
(a).eval() //[1, 2, 3]
);
console.log(
(b).eval() //[1, 2, 3]
);
Requirement 3 - Monoid Associative law
In addition to the prominent Identify structure, Monoids also satisfies Associative law
(a * b) * c === a * b * c === a * (b * c)
This simply means "flatten the list", in other words, the structure does not contain nested lists.
[a, [b, c]]
is no good.
Sample:
const L = a => L; // needs to enhance to satisfy the requirements
const a = (L)(1)(2);
const b = (L)(3)(4);
const c = (L)(99);
const ab = (a)(b);
const bc = (b)(c);
const abc1 = (ab)(c);
const abc2 = (a)(bc);
console.log(
abc1 === abc2 // true for Associative
);
console.log(
(ab).eval() //[1, 2, 3, 4]
);
console.log(
(abc1).eval() //[1, 2, 3, 4, 99]
);
console.log(
(abc2).eval() //[1, 2, 3, 4, 99]
);
That is all for 3 requirements to implement L
as a monoid.
This is a great challenge for functional programming to me, and actually I tried by myself for a while, but asking the previous questions, it's very good practice to share my own challenge and hear the people and read their elegant code.
Thank you.
a(L)
makes no sense given thata
might not be a function at all – Huntingtonfunction: identityType
,a(L)
makes sense. It's right identity as defined,and comment out ``` T[TYPE] = T; //right identity``` line, and how it breaks the logic/output. – Brothela
is always a function.const a = L(5);
numbers/any values are wrapped or "lift" to Type:L Similarity of String and Array such as"5"
` [5]`. It's been clarified from the first place in Q. – Efrema
is always supposed to be a function because that's not the way you use it. Ifa
is always supposed to be a function then you can't do(L)(1)
because1
is not a function. The point is that you're using your function inconsistently. From a functional programming point of view, what you're doing really makes no sense at all. – Octagonal((L)(1)(2))((L)(3)(4))
should be[[1,2],[3,4]]
but will instead be[1,2,3,4]
). There's a reason why we say that one function should only do one thing. This is it. – Octagonal[[1,2],[3,4]]
being created. Second, you've not implemented a difference list. Difference lists don't use accumulators. They use function composition. Third, type lifting is common in functional programming but you're not doing type lifting. You're combining two functions,snoc
andappend
, into one function and choosing when to do what using tags. Type lifting is when you have a separate function that lifts a value into a type. Being separate is needed for choosing when to lift. – Octagonalconcat
that include flattening there. If you simply add or map list there, you will have Magma without associative law, and binary tree structure instead of list. – Brothel