How to encode corecursion/codata in a strictly evaluated setting? - javascript

Corecursion means calling oneself on data at each iteration that is greater than or equal to what one had before. Corecursion works on codata, which are recursively defined values. Unfortunately, value recursion is not possible in strictly evaluated languages. We can work with explicit thunks though:
const Defer = thunk =>
({get runDefer() {return thunk()}})
const app = f => x => f(x);
const fibs = app(x_ => y_ => {
const go = x => y =>
Defer(() =>
[x, go(y) (x + y)]);
return go(x_) (y_).runDefer;
}) (1) (1);
const take = n => codata => {
const go = ([x, tx], acc, i) =>
i === n
? acc
: go(tx.runDefer, acc.concat(x), i + 1);
return go(codata, [], 0);
};
console.log(
take(10) (fibs));
While this works as expected the approach seems awkward. Especially the hideous pair tuple bugs me. Is there a more natural way to deal with corecursion/codata in JS?

I would encode the thunk within the data constructor itself. For example, consider.
// whnf :: Object -> Object
const whnf = obj => {
for (const [key, val] of Object.entries(obj)) {
if (typeof val === "function" && val.length === 0) {
Object.defineProperty(obj, key, {
get: () => Object.defineProperty(obj, key, {
value: val()
})[key]
});
}
}
return obj;
};
// empty :: List a
const empty = null;
// cons :: (a, List a) -> List a
const cons = (head, tail) => whnf({ head, tail });
// fibs :: List Int
const fibs = cons(0, cons(1, () => next(fibs, fibs.tail)));
// next :: (List Int, List Int) -> List Int
const next = (xs, ys) => cons(xs.head + ys.head, () => next(xs.tail, ys.tail));
// take :: (Int, List a) -> List a
const take = (n, xs) => n === 0 ? empty : cons(xs.head, () => take(n - 1, xs.tail));
// toArray :: List a -> [a]
const toArray = xs => xs === empty ? [] : [ xs.head, ...toArray(xs.tail) ];
// [0,1,1,2,3,5,8,13,21,34]
console.log(toArray(take(10, fibs)));
This way, we can encode laziness in weak head normal form. The advantage is that the consumer has no idea whether a particular field of the given data structure is lazy or strict, and it doesn't need to care.

Related

Emulating lisp list operations in js

What would be the proper way to define the cons, car, and cdr functions if we were to use javascript and functional programming? So far I have:
// CAR = λxy.x
// CDR = λxy.y
// CONS = λxy => ?
const car = a => b => a;
const cdr = a => b => b;
const cons = f => a => b;
let pair = cons(3)(4);
console.log(pair(car));
console.log(pair(cdr));
But my issue is pair(car) seems like an odd way to invoke the function. It seems like car(pair) seems like a better way but I'm not sure how to save the pair so it can be passed like that. How would that be done?
You can write them using continuations -
const cons = (a,b) => k => k(a,b)
const car = k => k((a,b) => a)
const cdr = k => k((a,b) => b)
const l = cons(1, cons(2, cons(3, null)))
console.log(car(l), car(cdr(l)), car(cdr(cdr(l))), cdr(cdr(cdr(l))))
// 1 2 3 null
Define more like list, toString, map, and filter -
const cons = (a,b) => k => k(a,b)
const car = k => k((a,b) => a)
const cdr = k => k((a,b) => b)
const list = (v, ...vs) => v == null ? null : cons(v, list(...vs))
const toString = l => l == null ? "Ø" : car(l) + "->" + toString(cdr(l))
console.log(toString(list(1,2,3,4,5)))
// 1->2->3->4->5->Ø
const square = x => x * x
const map = (f, l) =>
l == null
? null
: cons(f(car(l)), map(f, cdr(l)))
console.log(toString(map(square, list(1,2,3,4,5))))
// 1->4->9->16->25->Ø
const isOdd = x => x & 1
const filter = (f, l) =>
l == null
? null
: Boolean(f(car(l)))
? cons(car(l), filter(f, cdr(l)))
: filter(f, cdr(l))
console.log(toString(filter(isOdd, map(square, list(1,2,3,4,5)))))
// 1->9->25->Ø
Note you could just as easily write cons, car, and cdr abstractions using an array. Lambda is more fun but anything that fulfills the contract is acceptable. Being able to change the underlying representation like this and not require changes in other parts of your program is what makes data abstraction a powerful technique.
const cons = (a,b) => [a,b]
const car = k => k[0]
const cdr = k => k[1]
const l = cons(1, cons(2, cons(3, null)))
console.log(car(l), car(cdr(l)), car(cdr(cdr(l))), cdr(cdr(cdr(l))))
// 1 2 3 null
The following would be one way to define it, where car and cdr take one argument (a pair) instead of two:
// CAR = λxy.x or λp.p[0] where p is xy and p[0] means the first argument (x)
// CDR = λxy.y or λp.p[1] where p is xy and p[1] means the second argument (y)
// CONS = λxyf.xyf -- basically just saving x and y in a closure and deferring a function call
const first = a => b => a;
const second = a => b => b;
const getFirstFromPair = p => p(first), car = getFirstFromPair;
const getSecondFromPair = p => p(second), cdr = getSecondFromPair;
const cons = a => b => f => f(a)(b);
let pair = cons(3)(4);
console.log(car(pair));
console.log(cdr(pair));
Or, if we allow a pair as a single argument, such as (1,2):
const cons = (a,b) => f => f(a,b); // f is like a fake function call to do encapsulation
const car = f => f((a,b) => a); // and we have to un-do the function call here by passing it the first
const cdr = f => f((a,b) => b);
console.log(cdr(cons(1,2)));

How to work with javascript Map without mutations

I'm working in a functional way in my JS project.
That's also means I do not mutate object or array entities. Instead I always create a new instance and replace an old one.
e.g.
let obj = {a: 'aa', b: 'bb'}
obj = {...obj, b: 'some_new_value'}
The question is:
How to work in a functional (immutable) way with javascript Maps?
I guess I can use the following code to add values:
let map = new Map()
...
map = new Map(map).set(something)
But what about delete items?
I cannot do new Map(map).delete(something), because the result of .delete is a boolean.
P.S. I'm aware of existence of ImmutableJS, but I don't want to use it due to you never 100% sure if you are working now with a plain JS object, or with immutablejs' object (especially nested structures). And because of bad support of TypeScript, btw.
I cannot do new Map(map).delete(something);, because the result of .delete is a boolean.
You can use an interstitial variable. You can farm it out to a function if you like:
function map_delete(old_map, key_to_delete) {
const new_map = new Map(old_map);
new_map.delete(key_to_delete);
return new_map;
}
Or you can create get the entries in the map, filter them and create a new one from the result:
const new_map = new Map( Array.from(old_map.entries).filter( ([key, value]) => key !== something ) );
If you don't want to use a persistent map data structure, then you cannot get around mutations or have to conduct insanely inefficient shallow copies. Please note that mutations themselves aren't harmful, but only in conjunction with sharing the underlying mutable values.
If we are able to limit the way mutable values can be accessed, we can get safe mutable data types. They come at a cost, though. You cannot just use them as usual. As a matter of fact using them takes some time to get familiar with. It's a trade-off.
Here is an example with the native Map:
// MUTABLE
const Mutable = clone => refType => // strict variant
record(Mutable, app(([o, initialCall, refType]) => {
o.mutable = {
run: k => {
o.mutable.run = _ => {
throw new TypeError("illegal subsequent inspection");
};
o.mutable.set = _ => {
throw new TypeError("illegal subsequent mutation");
};
return k(refType);
},
set: k => {
if (initialCall) {
initialCall = false;
refType = clone(refType);
}
k(refType);
return o;
}
}
return o;
}) ([{}, true, refType]));
const mutRun = k => o =>
o.mutable.run(k);
const mutSet = k => o =>
o.mutable.set(k);
// MAP
const mapClone = m => new Map(m);
const mapDelx = k => m => // safe-in-place-update variant
mutSet(m_ =>
m_.has(k)
? m_.delete(k)
: m_) (m);
const mapGet = k => m =>
m.get(k);
const mapSetx = k => v => // safe-in-place-update variant
mutSet(m_ => m_.set(k, v));
const mapUpdx = k => f => // safe-in-place-update variant
mutSet(m_ => m_.set(k, f(m_.get(k))));
const MutableMap = Mutable(mapClone);
// auxiliary functions
const record = (type, o) => (
o[Symbol.toStringTag] = type.name || type, o);
const app = f => x => f(x);
const id = x => x;
// MAIN
const m = MutableMap(new Map([[1, "foo"], [2, "bar"], [3, "baz"]]));
mapDelx(2) (m);
mapUpdx(3) (s => s.toUpperCase()) (m);
const m_ = mutRun(Array.from) (m);
console.log(m_); // [[1, "foo"], [3, "BAZ"]]
try {mapSetx(4) ("bat") (m)} // illegal subsequent mutation
catch (e) {console.log(e.message)}
try {mutRun(mapGet(1)) (m)} // illegal subsequent inspection
catch (e) {console.log(e.message)}
If you take a closer look at Mutable you see it creates a shallow copy as well, but only once, initially. You can than conduct as many mutations as you want, until you inspect the mutable value the first time.
You can find an implementation with several instances in my scriptum library. Here is a post with some more background information on the concept.
I borrowed the concept from Rust where it is called ownership. The type theoretical background are affine types, which are subsumed under linear types, in case you are interested.
roll your own data structure
Another option is to write your own map module that does not depend on JavaScript's native Map. This completely frees us from its mutable behaviours and prevents making full copies each time we wish to set, update, or del. This solution gives you full control and effectively demonstrates how to implement any data structure of your imagination -
// main.js
import { fromEntries, set, del, toString } from "./map.js"
const a =
[["d",3],["e",4],["g",6],["j",9],["b",1],["a",0],["i",8],["c",2],["h",7],["f",5]]
const m =
fromEntries(a)
console.log(1, toString(m))
console.log(2, toString(del(m, "b")))
console.log(3, toString(set(m, "c", "#")))
console.log(4, toString(m))
We wish for the expected output -
map m, the result of fromEntries(a)
derivative of map m with key "b" deleted
derivative of map m with key "c" updated to "#"
map m, unmodified from the above operations
1 (a, 0)->(b, 1)->(c, 2)->(d, 3)->(e, 4)->(f, 5)->(g, 6)->(h, 7)->(i, 8)->(j, 9)
2 (a, 0)->(c, 2)->(d, 3)->(e, 4)->(f, 5)->(g, 6)->(h, 7)->(i, 8)->(j, 9)
3 (a, 0)->(b, 1)->(c, #)->(d, 3)->(e, 4)->(f, 5)->(g, 6)->(h, 7)->(i, 8)->(j, 9)
4 (a, 0)->(b, 1)->(c, 2)->(d, 3)->(e, 4)->(f, 5)->(g, 6)->(h, 7)->(i, 8)->(j, 9)
Time to fulfill our wishes and implement our map module. We'll start by defining what it means to be an empty map -
// map.js
const empty =
Symbol("map.empty")
const isEmpty = t =>
t === empty
Next we need a way to insert our entries into the map. This calls into existence, fromEntries, set, update, and node -
// map.js (continued)
const fromEntries = a =>
a.reduce((t, [k, v]) => set(t, k, v), empty)
const set = (t, k, v) =>
update(t, k, _ => v)
const update = (t, k, f) =>
isEmpty(t)
? node(k, f())
: k < t.key
? node(t.key, t.value, update(t.left, k, f), t.right)
: k > t.key
? node(t.key, t.value, t.left, update(t.right, k, f))
: node(k, f(t.value), t.left, t.right)
const node = (key, value, left = empty, right = empty) =>
({ key, value, left, right })
Next we'll define a way to get a value from our map -
// main.js (continued)
const get = (t, k) =>
isEmpty(t)
? undefined
: k < t.key
? get(t.left, k)
: k > t.key
? get(t.right, k)
: t.value
And now we'll define a way to delete an entry from our map, which also calls into existence concat -
// map.js (continued)
const del = (t, k) =>
isEmpty(t)
? t
: k < t.key
? node(t.key, t.value, del(t.left, k), t.right)
: k > t.key
? node(t.key, t.value, t.left, del(t.right, k))
: concat(t.left, t.right)
const concat = (l, r) =>
isEmpty(l)
? r
: isEmpty(r)
? l
: r.key < l.key
? node(l.key, l.value, concat(l.left, r), l.right)
: r.key > l.key
? node(l.key, l.value, l.left, concat(l.right, r))
: r
Finally we provide a way to visualize the map using toString, which calls into existence inorder. As a bonus, we'll throw in toArray -
const toString = (t) =>
Array.from(inorder(t), ([ k, v ]) => `(${k}, ${v})`).join("->")
function* inorder(t)
{ if (isEmpty(t)) return
yield* inorder(t.left)
yield [ t.key, t.value ]
yield* inorder(t.right)
}
const toArray = (t) =>
Array.from(inorder(t))
Export the module's features -
// map.js (continued)
export { empty, isEmpty, fromEntries, get, set, update, del, append, inorder, toArray, toString }
low hanging fruit
Your Map module is finished but there are some valuable features we can add without requiring much effort. Below we implement preorder and postorder map traversals. Additionally we add a second parameter to toString and toArray that allows you to choose which traversal to use. inorder is used by default -
// map.js (continued)
function* preorder(t)
{ if (isEmpty(t)) return
yield [ t.key, t.value ]
yield* preorder(t.left)
yield* preorder(t.right)
}
function* postorder(t)
{ if (isEmpty(t)) return
yield* postorder(t.left)
yield* postorder(t.right)
yield [ t.key, t.value ]
}
const toArray = (t, f = inorder) =>
Array.from(f(t))
const toString = (t, f = inorder) =>
Array.from(f(t), ([ k, v ]) => `(${k}, ${v})`).join("->")
export { ..., preorder, postorder }
And we can extend fromEntries to accept any iterable, not just arrays. This matches the functionality of Object.fromEntries and Array.from -
// map.js (continued)
function fromEntries(it)
{ let r = empty
for (const [k, v] of it)
r = set(r, k, v)
return r
}
Like we did above, we can add a second parameter which allows us to specify how the entries are added into the map. Now it works just like Array.from. Why Object.fromEntries doesn't have this behaviour is puzzling to me. Array.from is smart. Be like Array.from -
// map.js (continued)
function fromEntries(it, f = v => v)
{ let r = empty
let k, v
for (const e of it)
( [k, v] = f(e)
, r = set(r, k, v)
)
return r
}
// main.js
import { fromEntries, toString } from "./map.js"
const a =
[["d",3],["e",4],["g",6],["j",9],["b",1],["a",0],["i",8],["c",2],["h",7],["f",5]]
const z =
fromEntries(a, ([ k, v ]) => [ k.toUpperCase(), v * v ])
console.log(toString(z))
(A, 0)->(B, 1)->(C, 4)->(D, 9)->(E, 16)->(F, 25)->(G, 36)->(H, 49)->(I, 64)->(J, 81)
demo
Expand the snippet below to verify the results of our Map module in your own browser -
// map.js
const empty =
Symbol("map.empty")
const isEmpty = t =>
t === empty
const node = (key, value, left = empty, right = empty) =>
({ key, value, left, right })
const fromEntries = a =>
a.reduce((t, [k, v]) => set(t, k, v), empty)
const get = (t, k) =>
isEmpty(t)
? undefined
: k < t.key
? get(t.left, k)
: k > t.key
? get(t.right, k)
: t.value
const set = (t, k, v) =>
update(t, k, _ => v)
const update = (t, k, f) =>
isEmpty(t)
? node(k, f())
: k < t.key
? node(t.key, t.value, update(t.left, k, f), t.right)
: k > t.key
? node(t.key, t.value, t.left, update(t.right, k, f))
: node(k, f(t.value), t.left, t.right)
const del = (t, k) =>
isEmpty(t)
? t
: k < t.key
? node(t.key, t.value, del(t.left, k), t.right)
: k > t.key
? node(t.key, t.value, t.left, del(t.right, k))
: concat(t.left, t.right)
const concat = (l, r) =>
isEmpty(l)
? r
: isEmpty(r)
? l
: r.key < l.key
? node(l.key, l.value, concat(l.left, r), l.right)
: r.key > l.key
? node(l.key, l.value, l.left, concat(l.right, r))
: r
function* inorder(t)
{ if (isEmpty(t)) return
yield* inorder(t.left)
yield [ t.key, t.value ]
yield* inorder(t.right)
}
const toArray = (t) =>
Array.from(inorder(t))
const toString = (t) =>
Array.from(inorder(t), ([ k, v ]) => `(${k}, ${v})`).join("->")
// main.js
const a =
[["d",3],["e",4],["g",6],["j",9],["b",1],["a",0],["i",8],["c",2],["h",7],["f",5]]
const m =
fromEntries(a)
console.log(1, toString(m))
console.log(2, toString(del(m, "b")))
console.log(3, toString(set(m, "c", "#")))
console.log(4, toString(m))
console.log(5, get(set(m, "z", "!"), "z"))
functional module
Here's my little implementation of a persistent map module -
// map.js
import { effect } from "./func.js"
const empty = _ =>
new Map
const update = (t, k, f) =>
fromEntries(t).set(k, f(get(t, k)))
const set = (t, k, v) =>
update(t, k, _ => v)
const get = (t, k) =>
t.get(k)
const del = (t, k) =>
effect(t => t.delete(k))(fromEntries(t))
const fromEntries = a =>
new Map(a)
export { del, empty, fromEntries, get, set, update }
// func.js
const effect = f => x =>
(f(x), x)
// ...
export { effect, ... }
// main.js
import { fromEntries, del, set } from "./map.js"
const q =
fromEntries([["a",1], ["b",2]])
console.log(1, q)
console.log(2, del(q, "b"))
console.log(3, set(q, "c", 3))
console.log(4, q)
Expand the snippet below to verify the results in your own browser -
const effect = f => x =>
(f(x), x)
const empty = _ =>
new Map
const update = (t, k, f) =>
fromEntries(t).set(k, f(get(t, k)))
const set = (t, k, v) =>
update(t, k, _ => v)
const get = (t, k) =>
t.get(k)
const del = (t, k) =>
effect(t => t.delete(k))(fromEntries(t))
const fromEntries = a =>
new Map(a)
const q =
fromEntries([["a", 1], ["b", 2]])
console.log(1, q)
console.log(2, del(q, "b"))
console.log(3, set(q, "c", 3))
console.log(4, q)
1 Map(2) {a => 1, b => 2}
2 Map(1) {a => 1}
3 Map(3) {a => 1, b => 2, c => 3}
4 Map(2) {a => 1, b => 2}
object-oriented interface
If you want to use it in an object-oriented way, you can add a class wrapper around our plain functions. Here we call it Mapping because we don't want to clobber the native Map -
// map.js (continued)
class Mapping
{ constructor(t) { this.t = t }
update(k,f) { return new Mapping(update(this.t, k, f)) }
set(k,v) { return new Mapping(set(this.t, k, v)) }
get(k) { return get(this.t, k) }
del(k) { return new Mapping(del(this.t, k)) }
static empty () { return new Mapping(empty()) }
static fromEntries(a) { return new Mapping(fromEntries(a))
}
}
export default Mapping
// main.js
import Mapping from "./map"
const q =
Mapping.fromEntries([["a", 1], ["b", 2]]) // <- OOP class method
console.log(1, q)
console.log(2, q.del("b")) // <- OOP instance method
console.log(3, q.set("c", 3)) // <- OOP instance method
console.log(4, q)
Even though we're calling through OOP interface, our data structure still behaves persistently. No mutable state is used -
1 Mapping { t: Map(2) {a => 1, b => 2} }
2 Mapping { t: Map(1) {a => 1} }
3 Mapping { t: Map(3) {a => 1, b => 2, c => 3} }
4 Mapping { t: Map(2) {a => 1, b => 2} }

How to use the Trampoline type as the base monad of a transformer more efficiently?

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.
ArrayT m a = m (Array (m a))
The above type definition does not correctly interleave the underlying monad.
Following is an example value of the above data type.
of([of(1), of(2), of(3)])
There are several problems with this data type.
There is no effect for the end of the array.
The effects are not ordered. Hence, they can be executed in any order.
The underlying monad wraps the individual elements as well as the entire array. This is just wrong.
Following is an example value of the correct array monad transformer type.
of([1, of([2, of([3, of([])])])])
Note that.
There is an effect for the end of the array.
The effects are ordered. This is because the data type is defined recursively.
The underlying monad wraps the individual steps of the array. It doesn't wrap the entire array again.
Now, I understand why you want to define ArrayT m a = m (Array (m a)). If m = Identity then you get back an actual Array a, which supports random access of elements.
of([of(1), of(2), of(3)]) === [1, 2, 3]
On the other hand, the recursive array monad transformer type returns a linked list when m = Identity.
of([1, of([2, of([3, of([])])])]) === [1, [2, [3, []]]]
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.
The underlying monad must wrap at most one value at a time.
The underlying monad must be nested, to correctly order and interleave effects.
Coming back, the Trampoline monad is just the Free monad. We can define it as follows.
// pure : a -> Free a
const pure = value => ({ constructor: pure, value });
// bind : Free a -> (a -> Free b) -> Free b
const bind = monad => arrow => ({ constructor: bind, monad, arrow });
// thunk : (() -> Free a) -> Free a
const thunk = eval => ({ constructor: thunk, eval });
// MonadFree : Monad Free
const MonadFree = { pure, bind };
// evaluate : Free a -> a
const evaluate = expression => {
let expr = expression;
let stack = null;
while (true) {
switch (expr.constructor) {
case pure:
if (stack === null) return expr.value;
expr = stack.arrow(expr.value);
stack = stack.stack;
break;
case bind:
stack = { arrow: expr.arrow, stack };
expr = expr.monad;
break;
case thunk:
expr = expr.eval();
}
}
};
I'll also copy my implementation of the array monad transformer from my previous answer.
// Step m a = null | { head : a, tail : ListT m a }
// ListT m a = m (Step m a)
// nil : Monad m -> ListT m a
const nil = M => M.pure(null);
// cons : Monad m -> a -> ListT m a -> ListT m a
const cons = M => head => tail => M.pure({ head, tail });
// foldr : Monad m -> (a -> m b -> m b) -> m b -> ListT m a -> m b
const foldr = M => f => a => m => M.bind(m)(step =>
step ? f(step.head)(foldr(M)(f)(a)(step.tail)) : a);
Thus, when the underlying monad is Free then the operations are stack safe.
// replicate :: Number -> a -> ListT Free a
const replicate = n => x => n ?
cons(MonadFree)(x)(thunk(() => replicate(n - 1)(x))) :
nil(MonadFree);
// map : (a -> b) -> Free a -> Free b
const map = f => m => bind(m)(x => pure(f(x)));
// add : Number -> Free Number -> Free Number
const add = x => map(y => x + y);
// result : Free Number
const result = foldr(MonadFree)(add)(pure(0))(replicate(1000000)(1));
console.log(evaluate(result)); // 1000000
Putting it all together.
// pure : a -> Free a
const pure = value => ({ constructor: pure, value });
// bind : Free a -> (a -> Free b) -> Free b
const bind = monad => arrow => ({ constructor: bind, monad, arrow });
// thunk : (() -> Free a) -> Free a
const thunk = eval => ({ constructor: thunk, eval });
// MonadFree : Monad Free
const MonadFree = { pure, bind };
// evaluate : Free a -> a
const evaluate = expression => {
let expr = expression;
let stack = null;
while (true) {
switch (expr.constructor) {
case pure:
if (stack === null) return expr.value;
expr = stack.arrow(expr.value);
stack = stack.stack;
break;
case bind:
stack = { arrow: expr.arrow, stack };
expr = expr.monad;
break;
case thunk:
expr = expr.eval();
}
}
};
// Step m a = null | { head : a, tail : ListT m a }
// ListT m a = m (Step m a)
// nil : Monad m -> ListT m a
const nil = M => M.pure(null);
// cons : Monad m -> a -> ListT m a -> ListT m a
const cons = M => head => tail => M.pure({ head, tail });
// foldr : Monad m -> (a -> m b -> m b) -> m b -> ListT m a -> m b
const foldr = M => f => a => m => M.bind(m)(step =>
step ? f(step.head)(foldr(M)(f)(a)(step.tail)) : a);
// replicate :: Number -> a -> ListT Free a
const replicate = n => x => n ?
cons(MonadFree)(x)(thunk(() => replicate(n - 1)(x))) :
nil(MonadFree);
// map : (a -> b) -> Free a -> Free b
const map = f => m => bind(m)(x => pure(f(x)));
// add : Number -> Free Number -> Free Number
const add = x => map(y => x + y);
// result : Free Number
const result = foldr(MonadFree)(add)(pure(0))(replicate(1000000)(1));
console.log(evaluate(result)); // 1000000

Is there a way to avoid the trade-off between readability and performance when looping?

So this is a readable way (the code doesn't matter, what matters is the style):
arr.map().filter() // looping 2 times
And loops are considered as a faster way:
for(/* whatever */) {
// looping once, and doing all we need in the same loop
}
So my question is: is there a way, maybe from the functional programming world, to combine the readability of the former with the performance of the latter?
P.S. There is a trend to downvote such questions. If you want to, please write the reason as well.
Of course there is.
1st alternative: Transducer
const mapReduce = map => reduce => (acc, x) =>
reduce(acc, map(x));
const filterReduce = filter => reduce => (acc, x) =>
filter(x)
? reduce(acc, x)
: acc;
const transduce = (...ts) => xs =>
xs.reduce(ts.reduce(comp, id) (concat), []);
const comp = (f, g) =>
x => f(g(x));
const id = x => x;
const concat = (xs, ys) =>
xs.concat(ys);
const sqr = n => n * n;
const isOdd = n => n & 1 === 1;
const log = console.log;
// the upper code is usually library code
// so you don't have to deal with its complexity but only with its API
const tx = filterReduce(isOdd),
ty = mapReduce(sqr);
const r = transduce(tx, ty) ([1,2,3,4,5]); // filter/map in same iteration
log(r);
2nd alternative: Bare recursion with a tail call optimization effect
const loop = f => {
let acc = f();
while (acc && acc.type === tailRec)
acc = f(...acc.args);
return acc;
};
const tailRec = (...args) =>
({type: tailRec, args});
const comp = (f, g) => x =>
f(g(x));
const sqr = n => n * n;
const isOdd = n => n & 1 === 1;
const log = console.log;
// the upper code is usually library code
// so you don't have to deal with its complexity but only with its API
const r = loop((xs = [1,2,3,4,5], acc = [], i = 0) => {
if (i === xs.length)
return acc;
else
return tailRec( // filter/map in same iteration
xs,
isOdd(xs[i]) ? acc.concat(sqr(xs[i])) : acc,
i + 1);
});
log(r);
I'd say transducer are for normal, simpler iterations whereas recursion is suitable for more complex ones, for example when you need short circuiting (prematurely exiting).
Personally I don't think having some for-loops in your code makes it unreadable, but that's opinion based I suppose.
There are many ways to make your code more readable. If you're going to use this functionality often then you could create a method to add to Array.prototype - this way you can write the for-loop once and call it when you need it without having to see what you consider ugly code. Below is an example:
//This method will now be available to all Arrays instances:
Array.prototype.prettyLoop = function() {
console.log('All I do is execute a basic for-loop');
for (let i = 0; i < this.length; i++) {
console.log(this[i]);
}
};
//Call the method from your script
["a", 1, null, "x", 1989, false, {}].prettyLoop();

How to chain map and filter functions in the correct order

I really like chaining Array.prototype.map, filter and reduce to define a data transformation. Unfortunately, in a recent project that involved large log files, I could no longer get away with looping through my data multiple times...
My goal:
I want to create a function that chains .filter and .map methods by, instead of mapping over an array immediately, composing a function that loops over the data once. I.e.:
const DataTransformation = () => ({
map: fn => (/* ... */),
filter: fn => (/* ... */),
run: arr => (/* ... */)
});
const someTransformation = DataTransformation()
.map(x => x + 1)
.filter(x => x > 3)
.map(x => x / 2);
// returns [ 2, 2.5 ] without creating [ 2, 3, 4, 5] and [4, 5] in between
const myData = someTransformation.run([ 1, 2, 3, 4]);
My attempt:
Inspired by this answer and this blogpost I started writing a Transduce function.
const filterer = pred => reducer => (acc, x) =>
pred(x) ? reducer(acc, x) : acc;
const mapper = map => reducer => (acc, x) =>
reducer(acc, map(x));
const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
map: map => Transduce(mapper(map)(reducer)),
filter: pred => Transduce(filterer(pred)(reducer)),
run: arr => arr.reduce(reducer, [])
});
The problem:
The problem with the Transduce snippet above, is that it runs “backwards”... The last method I chain is the first to be executed:
const someTransformation = Transduce()
.map(x => x + 1)
.filter(x => x > 3)
.map(x => x / 2);
// Instead of [ 2, 2.5 ] this returns []
// starts with (x / 2) -> [0.5, 1, 1.5, 2]
// then filters (x < 3) -> []
const myData = someTransformation.run([ 1, 2, 3, 4]);
Or, in more abstract terms:
Go from:
Transducer(concat).map(f).map(g) == (acc, x) => concat(acc, f(g(x)))
To:
Transducer(concat).map(f).map(g) == (acc, x) => concat(acc, g(f(x)))
Which is similar to:
mapper(f) (mapper(g) (concat))
I think I understand why it happens, but I can't figure out how to fix it without changing the “interface” of my function.
The question:
How can I make my Transduce method chain filter and map operations in the correct order?
Notes:
I'm only just learning about the naming of some of the things I'm trying to do. Please let me know if I've incorrectly used the Transduce term or if there are better ways to describe the problem.
I'm aware I can do the same using a nested for loop:
const push = (acc, x) => (acc.push(x), acc);
const ActionChain = (actions = []) => {
const run = arr =>
arr.reduce((acc, x) => {
for (let i = 0, action; i < actions.length; i += 1) {
action = actions[i];
if (action.type === "FILTER") {
if (action.fn(x)) {
continue;
}
return acc;
} else if (action.type === "MAP") {
x = action.fn(x);
}
}
acc.push(x);
return acc;
}, []);
const addAction = type => fn =>
ActionChain(push(actions, { type, fn }));
return {
map: addAction("MAP"),
filter: addAction("FILTER"),
run
};
};
// Compare to regular chain to check if
// there's a performance gain
// Admittedly, in this example, it's quite small...
const naiveApproach = {
run: arr =>
arr
.map(x => x + 3)
.filter(x => x % 3 === 0)
.map(x => x / 3)
.filter(x => x < 40)
};
const actionChain = ActionChain()
.map(x => x + 3)
.filter(x => x % 3 === 0)
.map(x => x / 3)
.filter(x => x < 40)
const testData = Array.from(Array(100000), (x, i) => i);
console.time("naive");
const result1 = naiveApproach.run(testData);
console.timeEnd("naive");
console.time("chain");
const result2 = actionChain.run(testData);
console.timeEnd("chain");
console.log("equal:", JSON.stringify(result1) === JSON.stringify(result2));
Here's my attempt in a stack snippet:
const filterer = pred => reducer => (acc, x) =>
pred(x) ? reducer(acc, x) : acc;
const mapper = map => reducer => (acc, x) => reducer(acc, map(x));
const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
map: map => Transduce(mapper(map)(reducer)),
filter: pred => Transduce(filterer(pred)(reducer)),
run: arr => arr.reduce(reducer, [])
});
const sameDataTransformation = Transduce()
.map(x => x + 5)
.filter(x => x % 2 === 0)
.map(x => x / 2)
.filter(x => x < 4);
// It's backwards:
// [-1, 0, 1, 2, 3]
// [-0.5, 0, 0.5, 1, 1.5]
// [0]
// [5]
console.log(sameDataTransformation.run([-1, 0, 1, 2, 3, 4, 5]));
before we know better
I really like chaining ...
I see that, and I'll appease you, but you'll come to understand that forcing your program through a chaining API is unnatural, and more trouble than it's worth in most cases.
const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
map: map => Transduce(mapper(map)(reducer)),
filter: pred => Transduce(filterer(pred)(reducer)),
run: arr => arr.reduce(reducer, [])
});
I think I understand why it happens, but I can't figure out how to fix it without changing the “interface” of my function.
The problem is indeed with your Transduce constructor. Your map and filter methods are stacking map and pred on the outside of the transducer chain, instead of nesting them inside.
Below, I've implemented your Transduce API that evaluates the maps and filters in correct order. I've also added a log method so that we can see how Transduce is behaving
const Transduce = (f = k => k) => ({
map: g =>
Transduce(k =>
f ((acc, x) => k(acc, g(x)))),
filter: g =>
Transduce(k =>
f ((acc, x) => g(x) ? k(acc, x) : acc)),
log: s =>
Transduce(k =>
f ((acc, x) => (console.log(s, x), k(acc, x)))),
run: xs =>
xs.reduce(f((acc, x) => acc.concat(x)), [])
})
const foo = nums => {
return Transduce()
.log('greater than 2?')
.filter(x => x > 2)
.log('\tsquare:')
.map(x => x * x)
.log('\t\tless than 30?')
.filter(x => x < 30)
.log('\t\t\tpass')
.run(nums)
}
// keep square(n), forall n of nums
// where n > 2
// where square(n) < 30
console.log(foo([1,2,3,4,5,6,7]))
// => [ 9, 16, 25 ]
untapped potential
Inspired by this answer ...
In reading that answer I wrote, you overlook the generic quality of Trans as it was written there. Here, our Transduce only attempts to work with Arrays, but really it can work with any type that has an empty value ([]) and a concat method. These two properties make up a category called Monoids and we'd be doing ourselves a disservice if we didn't take advantage of transducer's ability to work with any type in this category.
Above, we hard-coded the initial accumulator [] in the run method, but this should probably be supplied as an argument – much like we do with iterable.reduce(reducer, initialAcc)
Aside from that, both implementations are essentially equivalent. The biggest difference is that the Trans implementation provided in the linked answer is Trans itself is a monoid, but Transduce here is not. Trans neatly implements composition of transducers in the concat method whereas Transduce (above) has composition mixed within each method. Making it a monoid allows us to rationalize Trans the same way do all other monoids, instead of having to understand it as some specialized chaining interface with unique map, filter, and run methods.
I would advise building from Trans instead of making your own custom API
have your cake and eat it too
So we learned the valuable lesson of uniform interfaces and we understand that Trans is inherently simple. But, you still want that sweet chaining API. OK, ok...
We're going to implement Transduce one more time, but this time we'll do so using the Trans monoid. Here, Transduce holds a Trans value instead of a continuation (Function).
Everything else stays the same – foo takes 1 tiny change and produces an identical output.
// generic transducers
const mapper = f =>
Trans(k => (acc, x) => k(acc, f(x)))
const filterer = f =>
Trans(k => (acc, x) => f(x) ? k(acc, x) : acc)
const logger = label =>
Trans(k => (acc, x) => (console.log(label, x), k(acc, x)))
// magic chaining api made with Trans monoid
const Transduce = (t = Trans.empty()) => ({
map: f =>
Transduce(t.concat(mapper(f))),
filter: f =>
Transduce(t.concat(filterer(f))),
log: s =>
Transduce(t.concat(logger(s))),
run: (m, xs) =>
transduce(t, m, xs)
})
// when we run, we must specify the type to transduce
// .run(Array, nums)
// instead of
// .run(nums)
Expand this code snippet to see the final implementation – of course you could skip defining a separate mapper, filterer, and logger, and instead define those directly on Transduce. I think this reads nicer tho.
// Trans monoid
const Trans = f => ({
runTrans: f,
concat: ({runTrans: g}) =>
Trans(k => f(g(k)))
})
Trans.empty = () =>
Trans(k => k)
const transduce = (t, m, xs) =>
xs.reduce(t.runTrans((acc, x) => acc.concat(x)), m.empty())
// complete Array monoid implementation
Array.empty = () => []
// generic transducers
const mapper = f =>
Trans(k => (acc, x) => k(acc, f(x)))
const filterer = f =>
Trans(k => (acc, x) => f(x) ? k(acc, x) : acc)
const logger = label =>
Trans(k => (acc, x) => (console.log(label, x), k(acc, x)))
// now implemented with Trans monoid
const Transduce = (t = Trans.empty()) => ({
map: f =>
Transduce(t.concat(mapper(f))),
filter: f =>
Transduce(t.concat(filterer(f))),
log: s =>
Transduce(t.concat(logger(s))),
run: (m, xs) =>
transduce(t, m, xs)
})
// this stays exactly the same
const foo = nums => {
return Transduce()
.log('greater than 2?')
.filter(x => x > 2)
.log('\tsquare:')
.map(x => x * x)
.log('\t\tless than 30?')
.filter(x => x < 30)
.log('\t\t\tpass')
.run(Array, nums)
}
// output is exactly the same
console.log(foo([1,2,3,4,5,6,7]))
// => [ 9, 16, 25 ]
wrap up
So we started with a mess of lambdas and then made things simpler using a monoid. The Trans monoid provides distinct advantages in that the monoid interface is known and the generic implementation is extremely simple. But we're stubborn or maybe we have goals to fulfill that are not set by us – we decide to build the magic Transduce chaining API, but we do so using our rock-solid Trans monoid which gives us all the power of Trans but also keeps complexity nicely compartmentalised.
dot chaining fetishists anonymous
Here's a couple other recent answers I wrote about method chaining
Is there any way to make a functions return accessible via a property?
Chaining functions and using an anonymous function
Pass result of functional chain to function
I think you need to change the order of your implementations:
const filterer = pred => reducer => (x) =>pred((a=reducer(x) )?x: undefined;
const mapper = map => reducer => (x) => map(reducer(x));
Then you need to change the run command to:
run: arr => arr.reduce((a,b)=>a.concat([reducer(b)]), []);
And the default reducer must be
x=>x
However, this way the filter wont work. You may throw undefined in the filter function and catch in the run function:
run: arr => arr.reduce((a,b)=>{
try{
a.push(reducer(b));
}catch(e){}
return a;
}, []);
const filterer = pred => reducer => (x) =>{
if(!pred((a=reducer(x))){
throw undefined;
}
return x;
};
However, all in all i think the for loop is much more elegant in this case...

Categories