I have an array transformer type that exhibits interleaved effect layers to ensure a lawful effect implementation. You can easily read the structure from the type's of operation const arrOfT = of => x => of([of(x)]).
The type implements an effectful fold as its basic operation. I use a left fold, because the underlying array type is inherently strict:
const arrFoldT = chain => f => init => mmx =>
chain(mmx) (mx => {
const go = (acc, i) =>
i === mx.length
? acc
: chain(mx[i]) (x =>
go(f(acc) (x), i + 1))
// ^^^^^^^^^^^^^^^^^^^^^ non-tail call position
return go(init, 0);
});
As you can see the implementation is not stack safe. However, stack safety is just another computational effect that can be encoded through a monad. I implemented one for the Trampoline type:
const monadRec = o => {
while (o.tag === "Chain")
o = o.f(o.x);
return o.tag === "Of"
? o.x
: _throw(new TypeError("unknown case"));
};
const recChain = mx => fm =>
mx.tag === "Chain" ? Chain(mx.x) (x => recChain(mx.f(x)) (fm))
: mx.tag === "Of" ? fm(mx.x)
: _throw(new TypeError("unknown case"));
const Chain = x => f =>
({tag: "Chain", f, x});
const Of = x =>
({tag: "Of", x});
While the implementations are straightforward the application is not. I am pretty sure I am applying it all wrong:
const mmx = Of(
Array(1e5)
.fill(Chain(1) (x => Of(x))));
// ^^^^^^^^^^^^ redundant continuation
const main = arrFoldT(recChain)
(acc => y => recMap(x => x + y) (acc))
(Of(0))
(mmx);
monadRec(main); // 100000
I need to use Chain when creating the large effectful array, because Of signals the the control flow to break out of the trampoline. With Chain on the other hand I have to specifiy a redundant continuation.
My first idea was to flip Chain's arguments and rely on partial application, but this doesn't work with the current implemenetation.
Is there a way to use the type more efficiently?
Here is a working example:
// ARRAYT
const arrFoldT = chain => f => init => mmx =>
chain(mmx) (mx => {
const go = (acc, i) =>
i === mx.length
? acc
: chain(mx[i]) (x =>
go(f(acc) (x), i + 1))
return go(init, 0);
});
// TRAMPOLINE
const monadRec = o => {
while (o.tag === "Chain")
o = o.f(o.x);
return o.tag === "Of"
? o.x
: _throw(new TypeError("unknown case"));
};
const Chain = x => f =>
({tag: "Chain", f, x});
const Of = x =>
({tag: "Of", x});
// Functor
const recMap = f => tx =>
Of(f(tx.x));
// Monad
const recChain = mx => fm =>
mx.tag === "Chain" ? Chain(mx.x) (x => recChain(mx.f(x)) (fm))
: mx.tag === "Of" ? fm(mx.x)
: _throw(new TypeError("unknown case"));
const recOf = Of;
// MAIN
const mmx = Of(
Array(1e5)
.fill(Chain(1) (x => Of(x))));
const main = arrFoldT(recChain)
(acc => y => recMap(x => x + y) (acc))
(Of(0))
(mmx);
console.log(
monadRec(main)); // 100000
First, the definition of your array monad transformer is wrong.
The above type definition does not correctly interleave the underlying monad.
Following is an example value of the above data type.
There are several problems with this data type.
Following is an example value of the correct array monad transformer type.
Note that.
Now, I understand why you want to define
ArrayT m a = m (Array (m a)). Ifm = Identitythen you get back an actualArray a, which supports random access of elements.On the other hand, the recursive array monad transformer type returns a linked list when
m = Identity.However, there's no way to create a lawful array monad transformer type which also returns an actual array when the underlying monad is
Identity. This is because monad transformers are inherently algebraic data structures, and arrays are not algebraic.The closest you can get is by defining
ArrayT m a = Array (m a). However, this would only satisfy the monad laws when the underlying monad is commutative.Just remember, when defining a monad transformer data type.
Coming back, the
Trampolinemonad is just theFreemonad. We can define it as follows.I'll also copy my implementation of the array monad transformer from my previous answer.
Thus, when the underlying monad is
Freethen the operations are stack safe.Putting it all together.