Last active
February 21, 2023 09:35
-
-
Save scorbiclife/a336b958b259bb66bfa05ac5a18b02b2 to your computer and use it in GitHub Desktop.
Do Notation with For Loops
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
// I'm curious about your thoughts on using for loops for do-notation syntax. | |
// How does `idresultExpr`, `oresultExpr`, and `presultExpr` look? | |
// The identity monad | |
// `iterOfValue` is a generator function. | |
// it itself is a function, but it can return iterable iterators | |
function* iterOfValue<T>(x: T): Iterable<T> { | |
yield x; | |
} | |
for (const m of iterOfValue(3)) { | |
for (const n of iterOfValue(m)) { | |
console.log(n); | |
} | |
} | |
////////// this ////////// | |
function* idresultExpr() { | |
for (const m of iterOfValue(3)) { | |
for (const n of iterOfValue(m)) { | |
yield n; | |
} | |
} | |
} | |
function valueOfIter<T>(ix: Iterable<T>): T { | |
for (const x of ix) { | |
return x; | |
} | |
throw new Error("Unreachable state from valid iterator"); | |
} | |
const idresult = valueOfIter(idresultExpr()); | |
console.log(idresult); | |
// The list monad | |
// does exactly what you think it would do so I'll just move on. | |
// The maybe monad | |
type Option<T> = { type: "some"; value: T } | { type: "none" }; | |
const optionOf = { | |
some<T>(value: T): Option<T> { | |
return { type: "some", value }; | |
}, | |
none(): Option<never> { | |
return { type: "none" }; | |
}, | |
}; | |
function* iterOfOption<T>(ox: Option<T>): Iterable<T> { | |
switch (ox.type) { | |
case "some": { | |
yield* iterOfValue(ox.value); | |
return; | |
} | |
case "none": { | |
return; | |
} | |
} | |
} | |
const ox = optionOf.some(1); | |
const oy = optionOf.some(2); | |
////////// this ////////// | |
function* oresultExpr() { | |
for (const x of iterOfOption(ox)) { | |
for (const y of iterOfOption(oy)) { | |
yield x + y; | |
} | |
} | |
} | |
function optionOfIter<T>(ix: Iterable<T>): Option<T> { | |
for (const x of ix) { | |
return optionOf.some(x); | |
} | |
return optionOf.none(); | |
} | |
const oresult = optionOfIter(oresultExpr()); | |
console.log(oresult); | |
// future monad | |
// I had to use a for await loop... | |
// because I cannot put a normal for loop | |
// into a microtask queue by normal means | |
// also I'm only dealing with resolved promises for simplicity | |
// and because resolve/reject is irrelevant for this demonstration | |
// `iterOfPromise` is an asynchronous generator function. | |
// it itself is a function and is synchronous, | |
// but it returns an asynciterable asynciterator. | |
async function* aiterOfPromise<T>(px: Promise<T>): AsyncIterable<T> { | |
const x = await px; | |
yield x; | |
} | |
const px = Promise.resolve(1); | |
const py = Promise.resolve(2); | |
////////// this ////////// | |
async function* presultExpr() { | |
for await (const x of aiterOfPromise(px)) { | |
for await (const y of aiterOfPromise(py)) { | |
yield x + y; | |
} | |
} | |
} | |
async function promiseOfAiter<T>(aix: AsyncIterable<T>): Promise<T> { | |
for await (const x of aix) { | |
return x; | |
} | |
throw new Error("Unreachable state from valid iterator"); | |
} | |
const presult = promiseOfAiter(presultExpr()); | |
presult.then(console.log); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can I use this in production code?
Please don't. I just wanted to see how the syntax looks for others.
What's different from callback hell?
Functionally nothing, but I'm hoping it's easier to understand and reason about.
I'm also hoping that it's more intuitive to think of doing this:
than to think of doing this a la expressjs/cps/etc:
kudos to express btw