Skip to content

Instantly share code, notes, and snippets.

@bens
Last active September 7, 2020 01:14
Show Gist options
  • Save bens/a13950e829bcef03047a60b13c86b9aa to your computer and use it in GitHub Desktop.
Save bens/a13950e829bcef03047a60b13c86b9aa to your computer and use it in GitHub Desktop.
Compositional folds in typescript
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
);
@bens
Copy link
Author

bens commented Sep 4, 2020

@bens
Copy link
Author

bens commented Sep 4, 2020

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