Conversation
ca590f7 to
b219cd0
Compare
ff8e682 to
bab7be0
Compare
| export function call<T>(callable: () => Operation<T>): Operation<T>; | ||
| export function call<T>(callable: () => Promise<T>): Operation<T>; | ||
| export function call<T>(callable: () => T): Operation<T>; | ||
| export function call<T>(callable: Callable<T>): Operation<T | Iterable<T>> { |
There was a problem hiding this comment.
Why might this return an Operation<Iterable>? If you have call(() => []) which override will it select? The one on line 68 or 69?
There was a problem hiding this comment.
You're right, removed. Probably old code from when I was experimenting.
cowboyd
left a comment
There was a problem hiding this comment.
I think this is the best option we have. As noted, there is a small discrepancy in the types and the runtime behavior in the event that you call(() => instructions) where instructions is an array of Instruction.
This is a very tiny edge case.
The only remaining decision is whether to support bare values. In other words, should callable be:
| (() => T))
| (() => Promise<T>))
| (() => Operation<T>))or
| T
| Operation<T>
| Promise<T>
| (() => T))
| (() => Promise<T>))
| (() => Operation<T>))It feels like we should support one or the other, but not somewhere in between.
| function isPromise(p: unknown): boolean { | ||
| if (!p) return false; | ||
| return isFunc((p as PromiseLike<unknown>).then); | ||
| } |
There was a problem hiding this comment.
Can we use guards where possible? It saves a little casting when you use them.
| function isPromise(p: unknown): boolean { | |
| if (!p) return false; | |
| return isFunc((p as PromiseLike<unknown>).then); | |
| } | |
| function isPromise<T>(p: unknown): p is Promise<T> { | |
| if (!p) return false; | |
| return isFunc((p as Promise<T>).then); | |
| } |
| send: (p) => call(() => signal.send(p)), | ||
| close: (p) => call(() => signal.close(p)), |
There was a problem hiding this comment.
Since we can't get rid of lift, let's continue to use it here. We can revisit the best way to do this later, but lift is not going to create a frame which is preferable.
7db0609 to
b5f6297
Compare
| })).resolves.toEqual(eq); | ||
| }); | ||
|
|
||
| it("evaluates a string iterator", async () => { |
There was a problem hiding this comment.
I wouldn't call this a string iterator, but just a function returning a string.
| | T | ||
| | (() => Operation<T>) | ||
| | (() => Promise<T>); | ||
| | (() => Iterator<T>) |
There was a problem hiding this comment.
Shouldn't this be Iterator<Instruction, T>?
dccc3ac to
374344f
Compare
| function isFunc(f: unknown): boolean { | ||
| return typeof f === "function"; | ||
| } | ||
| function isPromise<T>(p: unknown): p is Promise<T> { | ||
| if (!p) return false; | ||
| return isFunc((p as Promise<T>).then); | ||
| } | ||
| // iterator must implement both `.next` and `.throw` | ||
| // built-in iterators are not considered iterators to `call()` | ||
| function isInstructionIterator<T>(it: unknown): it is Iterator<Instruction, T> { | ||
| if (!it) return false; | ||
| return isFunc((it as Iterator<Instruction, T>).next) && | ||
| isFunc((it as Iterator<Instruction, T>).throw); | ||
| } | ||
| function isIterable<T>(it: unknown): it is Iterable<T> { | ||
| if (!it) return false; | ||
| return typeof (it as Iterable<T>)[Symbol.iterator] === "function"; | ||
| } | ||
|
|
There was a problem hiding this comment.
Can we move these private functions to the bottom of the file and lead with the implementation of call()?
| * Pause the current operation, and run a promises, async functions, or | ||
| * operations within a new scope. The calling operation will resumed (or errored) |
There was a problem hiding this comment.
Looks like there's a slight grammatical error here. Might as well fix this now.
| * Pause the current operation, and run a promises, async functions, or | |
| * operations within a new scope. The calling operation will resumed (or errored) | |
| * Pause the current operation, and run a promise, async function, or | |
| * operation within a new scope. The calling operation will resumed (or errored) |
We also export `Callable` so it is available in userland.
98043d4 to
0446d53
Compare
feat: `call` accepts plain functions
Motivation
As library developers, we might want to require the user to understand delimited continuations or any of the constructs created by
effection. Sometimes we want to let the end-user provide anything reasonable wecall()knows how to reflect what was passed into the function and it will handle it automatically. The most obvious example isPromise. We don't have to require the user to convert aPromiseinto anOperation.Being able to pass a vanilla function into
call()would also be nice since it is trivial for us to detect and accept.So in summary, we want to support the following variables to
call():Approach
We try to inspect the type of the variables passed into
call()and figure out how to evaluate it.