Public
Edited
Jan 7, 2024
Insert cell
Insert cell
import {log} from '@sorig/console-log-without-leaving-your-notebook'
Insert cell
import {logTable} from '@sorig/console-log-without-leaving-your-notebook'
Insert cell
logTable(10)
Insert cell
stepGen(test1())
Insert cell
stepGen(test2())
Insert cell
// delimited continuations: https://gist.github.com/yelouafi/44f5ed9bcdd1100b1902bcdb80aa32da
function stepGen(gen, arg) {
const {done, value} = gen.next(arg)
if(done) {
if(gen._return) {
stepGen(gen._return, value)
}
} else if(typeof value === 'function') {
// example:
// yield gen => setTimeout(() => stepGen(gen, null), 1000)
value(gen)
} else if(typeof value.next === 'function') {
// or yield a child Generator
value._return = gen
value._reset = gen._reset
stepGen(value, null)
}
}
Insert cell
// reset is used to delimit the continuation
// eventually captured by shift
function reset(f) {
return gen => {
const genReset = f()
genReset._return = gen
genReset._reset = genReset
stepGen(genReset, null)
}
}
Insert cell
// shift captures the current continuation up
// to the innermost enclosing reset
function shift(f) {
return gen => {
const genReset = gen._reset
const genShift = f(v => genK => {
genReset._return = genK
stepGen(gen, v)
})
genShift._return = genReset._return
stepGen(genShift, null)
}
}
Insert cell
function* test1() {
const x = yield reset(function*() {
// no shift; reset returns as a normal function
return 2
})
console.log('test1: ', x)
}
Insert cell
function* test2() {
const x = yield reset(function*() {
const a = 2
const b = yield shift(function*(k) {
// shift doesnt invoke the continuation k
// this will be the result of the whole reset block
return 3
})
return a + b
})
console.log('test2: ', x)
}
Insert cell
// goal code:
{
const x = createList([3, 4, 5]);
// const y = createList([0, 10]);

const mul = createListFn(() => x.get() * 3);

runList(() => { console.log(mul.get()) });
// should print [9, 12, 15]
}
Insert cell
/*

How is this even gonna work???

we call mul.get()

mul.get() is running.
The eval context stack looks like this: () => x.get() * 3
It encounters x.get().
x.get() works like this: shift((k) => list.flatMap((x) => k(x)))
"shift captures (and erases) the evaluation context up to the nearest dynamically enclosing reset"

The current eval context stack is () => x.get() * 3 and x represents [3, 4, 5] so this computation resolves to
[3, 4, 5].flatMap((x) => x * 3) (we hope. but actually we haven't written the program this way!)

bind(k, ma)
bind(() => x.get() * 3, [3, 4, 5])
// for each val in [3, 4, 5], x.set(val) && call () => x.get() * 3. accumulate result


suppose we instead do () => x.get() + y.get()


we call x.get()
x.get() is running
The eval context is: () => x.get() + y.get()
We encounter x.get()
// for each val in [3, 4, 5], x.set(val) && call () => x.get() + y.get(). accumulate result
but while accumulating, we encounter y.get()

*/
Insert cell
retList = (x) => [x]
Insert cell
bindList = (f, xs) => xs.map((x) => f(x))
Insert cell
// escape = shift
// capture = reset
// (https://gist.github.com/sebfisch/2235780)
Insert cell
// reflect
create = ({ ret, bind }, ma) => {
return escape_((k) => bind(k, ma)) // take whatever we were computing and apply it to our input
}
Insert cell
// reify
run = ({ ret, bind }, thunk) => {
// return bind(ret, reset(() => ret(thunk)))
// apply monad laws...
return capture_(() => ret(thunk)) // set computation boundary
}
Insert cell
listMonad = ({
ret: (x /* T */) /* : List<T> */ => [x],
bind: (f /* (x: T) => List<U> */, xs /* List<T> */) /* : List<U> */ => xs.flatMap((x) => f(x))
})
Insert cell
// apply run to list monad
runList = (thunk) => {
return reset(() => [thunk])
}
Insert cell
// apply reflect to list monad
createList = (list) => {
return shift((k) => list.flatMap((x) => k(x)))
}
Insert cell
context = []
Insert cell
class List {
constructor(list) {
this.value = list
}
}
Insert cell
// createList = (list) => new List(list)
Insert cell
class ListFn {
// reify
constructor(fn) {
this.fn = fn
}

// run
get() {
context.push(this);
try {
this.fn();
} finally {
context.pop();
}
}
}
Insert cell
createListFn = (fn) => new ListFn(fn)
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more