Skip to content

Instantly share code, notes, and snippets.

@briancavalier briancavalier/fx.ts
Last active Mar 17, 2019

Embed
What would you like to do?
Small encoding of algebraic effects in Typescript (probably works in Flow, too, with syntax tweaks) used in helicopter: https://github.com/briancavalier/helicopter/blob/master/packages/core/src/fx.ts
export type Cancel = void | ((k: (r: void) => void) => void)
export const runCancel = (c: Cancel, k: (r: void) => void): void =>
c ? c(k) : k()
export type Fx<H, A> = (handler: H, k: (a: A) => void) => Cancel
export type Pure<A> = Fx<{}, A>
export const handle = <H0, H1, A>(fx: Fx<H0 & H1, A>, h1: H1): Fx<H0, A> =>
(h0, k) => fx({ ...h0, ...h1 } as H0 & H1, k)
export const runPure = <A>(fx: Fx<{}, A>, k: (a: A) => void = () => {}): Cancel =>
fx({}, k)
export const pure = <A> (a: A): Pure<A> =>
(_, k) => k(a)
export const map = <H, A, B> (f: (a: A) => B, fx: Fx<H, A>): Fx<H, B> =>
(h, k) => fx(h, a => k(f(a)))
export const mapTo = <H, A, B> (b: B, fx: Fx<H, A>): Fx<H, B> =>
map(_ => b, fx)
export const chain = <HA, HB, A, B>(f: (a: A) => Fx<HA, B>, fx: Fx<HB, A>): Fx<HA & HB, B> =>
(env, k) => {
let cancel = fx(env, a => {
cancel = f(a)(env, k)
})
return k => runCancel(cancel, k)
}
export const forever = <H, A>(fx: Fx<H, A>): Fx<H, never> =>
chain(() => forever(fx), fx)
import { Cancel, chain, forever, Fx, handle, map, runPure } from './fx'
import { EOL } from 'os'
import { createInterface } from 'readline'
// Print effect and constructor
type Print = {
print(s: string, k: (r: void) => void): Cancel
}
const print = (s: string): Fx<Print, void> =>
({ print }, k) => print(s, k)
// Read effect and constructor
type Read = {
read(k: (r: string) => void): Cancel
}
const read: Fx<Read, string> =
({ read }, k) => read(k)
// Helper to append newlines
const addEOL = (s: string): string => `${s}${EOL}`
// Effectful computation that prints a prompt, reads
// user input and prints it.
const echo: Fx<Print & Read, void> =
chain(() => chain(print, map(addEOL, read)), print('> '))
// To run echo, we need to provide handlers for the
// Read and Print effect
// We'll use node's readline to implement a Read handler
const rl = createInterface({
input: process.stdin,
output: process.stdout
})
// Implement the Print and Read effect handlers
const handlers: Print & Read = {
print: (s, k): Cancel => k(void process.stdout.write(s)),
read: k => {
rl.once('line', k)
return () => rl.removeListener('line', k)
}
}
// Loop echo forever using the effect handlers
runPure(handle(forever(echo), handlers))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.