Last active
September 7, 2020 01:14
-
-
Save bens/a13950e829bcef03047a60b13c86b9aa to your computer and use it in GitHub Desktop.
Compositional folds in typescript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default class Fold<A, B> { | |
private constructor( | |
private readonly state: any, | |
private readonly step: (x: A, st: any) => any, | |
private readonly done: (st: any) => B | |
) {} | |
public static of<A, B, S>( | |
state: S, | |
step: (x: A, st: S) => S, | |
done: (st: S) => B | |
): Fold<A, B> { | |
return new Fold(state, step, done); | |
} | |
public static ofObject<A, B>( | |
folds: { [P in keyof B]: Fold<A, B[P]> } | |
): Fold<A, B> { | |
type St = { [P in keyof B]: any }; | |
const keys: string[] = Object.keys(folds); | |
const state: St = {} as St; | |
keys.forEach(k => (state[k] = folds[k].state)); | |
const step = (x: A, st: St) => { | |
const out: St = {} as St; | |
keys.forEach(k => (out[k] = folds[k].step(x, st[k]))); | |
return out; | |
}; | |
const done = (st: St) => { | |
const out: B = {} as B; | |
keys.forEach(k => (out[k] = folds[k].done(st[k]))); | |
return out; | |
}; | |
return new Fold(state, step, done); | |
} | |
public run(x: A): Fold<A, B> { | |
return new Fold(this.step(x, this.state), this.step, this.done); | |
} | |
public extract(): B { | |
return this.done(this.state); | |
} | |
public runMany(xs: A[]): Fold<A, B> { | |
let st: any = this.state; | |
xs.forEach(x => { | |
st = this.step(x, st); | |
}); | |
return new Fold(st, this.step, this.done); | |
} | |
public premap<C>(f: (_: C) => A): Fold<C, B> { | |
return new Fold(this.state, (x, st) => this.step(f(x), st), this.done); | |
} | |
public map<C>(f: (_: B) => C): Fold<A, C> { | |
return new Fold(this.state, this.step, x => f(this.done(x))); | |
} | |
public filter(f: (_: A) => boolean): Fold<A, B> { | |
return new Fold( | |
this.state, | |
(x, st) => (f(x) ? this.step(x, st) : st), | |
this.done | |
); | |
} | |
} | |
export const counter: Fold<unknown, number> = Fold.of( | |
0, | |
(_, count) => 1 + count, | |
x => x | |
); | |
export const summer: Fold<number, number> = Fold.of( | |
0, | |
(n, sum) => n + sum, | |
x => x | |
); | |
export function last<A>(): Fold<A, A | null> { | |
return Fold.of( | |
null as A | null, | |
(x, _) => x, | |
x => x | |
); | |
} | |
export const fibber: Fold<unknown, number> = Fold.of( | |
[0, 1], | |
(_, [a, b]) => [b, b + a], | |
([a, _]) => a | |
); |
And to use it to find the length of a list, as well as the length of the list times two, in a single pass over the data.
let longlist = [1];
for (let i = 0; i < 25; i++) {
longlist = longlist.concat(longlist);
}
const myfold = Fold.ofObject({ a: counter, b: counter.map(x => x * 2) });
console.log(JSON.stringify(myfold.runMany(longlist).extract()));
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://squing.blogspot.com/2008/11/beautiful-folding.html