-
Notifications
You must be signed in to change notification settings - Fork 124
Description
In short
The following code emulates the ? operator:
// `returnError` and `block` are utility functions for emulation
function returnError<T, E>(result: Result<T, E>): Generator<Err<never, E>, T> {
return function*() {
if (result.isOk()) {
return result.value
}
yield err(result.error) // Calling err(result.error) for changing the type from Err<T, E> to Err<never, E>
// As long as being consumed by `yield*` in `block`,
// `next()` of this generator is called just once, so this should never happen
throw new Error("THIS SHOULD NEVER HAPPEN")
}()
}
function block<T, E>(
body: () => Generator<Err<never, E>, Result<T, E>>
) {
return body().next().value
}
// Example usages
// type of x is Result<"foo", "bar">, and its value is ok("foo")
const x = block<"foo", "bar">(
function*() {
const r: Result<"baz", "bar"> = ok("baz")
const okBaz = yield* returnError(r) // here the type of okBaz is "baz", and the value on execution time is so, too
return ok("foo") // and finally returns this value
}
)
// type of y is Result<"foo", "bar">, and its value is err("bar")
const y = block<"foo", "bar">(
function*() {
const r: Result<"baz", "bar"> = err("bar")
const okBaz = yield* returnError(r) // the type of okBaz is "baz", but this block returns err("bar") here
return ok("foo") // and this line is not evaluated
}
)
Of course you can use yield*
s and return
s as many as you want in a block
.
Description
An expression yield* gen
in a generator function, here gen
is a generator, yields what are yielded in gen
, and is evaluated to what is returned in gen
, without yielding the returned value. (Please see MDN)
As returnError
is defined to return a generator that yields the argument if the argument is error and otherwise returns its unwrapped (ok's) value, yield* returnError(r)
yields r
if r
is error, and otherwise is evaluated to r.value
without yielding anything.
Finally, as block
is defined to call next()
on the argument exactly once and return that value, block(function*(){STATEMENTS})
is evaluated to the first occurrence in STATEMENTS
of either yield* returnError(some_error)
or return some_result
.
More integrated version (including async support)
It makes no sense to import returnError
or block
independently, so I combined them into single object block
.
async block is also implemented by overload. It can be used as its type implies.
export const block:
& (<T, E>(
body: () => Generator<Err<never, E>, Result<T, E>>
) => Result<T, E>)
& (<T, E>(
body: () => AsyncGenerator<Err<never, E>, Result<T, E>>
) => Promise<Result<T, E>>)
& {
returnError: <T, E>(result: Result<T, E>) => Generator<Err<never, E>, T>
}
= (() => {
function fBlock<T, E>(
body: () => Generator<Err<never, E>, Result<T, E>>
): Result<T, E>
function fBlock<T, E>(
body: () => AsyncGenerator<Err<never, E>, Result<T, E>>
): Promise<Result<T, E>>
function fBlock<T, E>(
body:
| (() => Generator<Err<never, E>, Result<T, E>>)
| (() => AsyncGenerator<Err<never, E>, Result<T, E>>)
) {
const n = body().next()
if (n instanceof Promise) {
return n.then(r => r.value)
}
return n.value
}
return Object.assign(
fBlock,
{
returnError: <T, E>(result: Result<T, E>): Generator<Err<never, E>, T> => {
return function*() {
if (result.isOk()) {
return result.value
}
yield err(result.error)
throw new Error("THIS SHOULD NEVER HAPPEN")
}()
}
},
)
})()
Question
Should I make a PR? I'm willing to make a PR if this is useful for this project.
If so, do anyone have any opinion how to integrate this to the existing APIs? (naming, module structure, or anything else)
Of course, any comments would be appreciated.