Skip to content

Commit

Permalink
Begin V4
Browse files Browse the repository at this point in the history
  • Loading branch information
cowboyd committed Oct 23, 2024
1 parent 4982887 commit 8be4415
Show file tree
Hide file tree
Showing 65 changed files with 1,682 additions and 1,596 deletions.
5 changes: 0 additions & 5 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,20 @@
},
"exclude": [
"build",
"website",
"www",
"packages"
]
},
"fmt": {
"exclude": [
"build",
"website",
"www",
"packages",
"CODE_OF_CONDUCT.md",
"README.md"
]
},
"test": {
"exclude": [
"build",
"packages"
]
},
"compilerOptions": {
Expand Down
4 changes: 2 additions & 2 deletions lib/abort-signal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Operation } from "./types.ts";
import { resource } from "./instructions.ts";
import { resource } from "./resource.ts";

/**
* Create an
Expand Down Expand Up @@ -29,7 +29,7 @@ import { resource } from "./instructions.ts";
* ```
*/
export function useAbortSignal(): Operation<AbortSignal> {
return resource(function* AbortSignal(provide) {
return resource(function* (provide) {
let controller = new AbortController();
try {
yield* provide(controller.signal);
Expand Down
34 changes: 34 additions & 0 deletions lib/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Err, Ok } from "./result.ts";
import { Effect, Operation } from "./types.ts";

interface Resolver<T> {
(resolve: (value: T) => void, reject: (error: Error) => void): () => void;
}

export function action<T>(resolver: Resolver<T>, desc?: string): Operation<T> {
return {
*[Symbol.iterator]() {
let action: Effect<T> = {
description: desc ?? "action",
enter: (settle) => {
let resolve = (value: T) => {
settle(Ok(value));
};
let reject = (error: Error) => {
settle(Err(error));
};
let discard = resolver(resolve, reject);
return (discarded) => {
try {
discard();
discarded(Ok());
} catch (error) {
discarded(Err(error));
}
};
},
};
return (yield action) as T;
},
};
}
31 changes: 17 additions & 14 deletions lib/all.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Operation, Task, Yielded } from "./types.ts";
import { spawn } from "./instructions.ts";
import { call } from "./call.ts";
import { spawn } from "./spawn.ts";
import { encapsulate, trap } from "./task.ts";

/**
* Block and wait for all of the given operations to complete. Returns
Expand All @@ -27,20 +27,23 @@ import { call } from "./call.ts";
* @param ops a list of operations to wait for
* @returns the list of values that the operations evaluate to, in the order they were given
*/
export function all<T extends readonly Operation<unknown>[] | []>(
export function* all<T extends readonly Operation<unknown>[] | []>(
ops: T,
): Operation<All<T>> {
return call(function* () {
let tasks: Task<unknown>[] = [];
for (let operation of ops) {
tasks.push(yield* spawn(() => operation));
}
let results = [];
for (let task of tasks) {
results.push(yield* task);
}
return results as All<T>;
});
return yield* trap(() =>
encapsulate(function* (): Operation<All<T>> {
let tasks: Task<unknown>[] = [];

for (let operation of ops) {
tasks.push(yield* spawn(() => operation));
}
let results = [];
for (let task of tasks) {
results.push(yield* task);
}
return results as All<T>;
})
);
}

/**
Expand Down
175 changes: 45 additions & 130 deletions lib/call.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Instruction, Operation } from "./types.ts";
import { action } from "./instructions.ts";
import { pause } from "./pause.ts";
import { constant } from "./constant.ts";
import { action } from "./action.ts";
import { Operation } from "./types.ts";

/**
* A uniform integration type representing anything that can be evaluated
Expand All @@ -24,30 +24,21 @@ import { pause } from "./pause.ts";
* await run(() => hello(function*() { return "world!" })); "hello world!";
* ```
*/
export type Callable<T> =
| Operation<T>
| Promise<T>
| (() => Operation<T>)
| (() => Promise<T>)
| (() => T);
export interface Callable<
T extends Operation<unknown> | Promise<unknown> | unknown,
TArgs extends unknown[] = [],
> {
(...args: TArgs): T;
}

/**
* Pause the current operation, then runs a promise, async function, plain function,
* or operation within a new scope. The calling operation will be resumed (or errored)
* once call is completed.
* Pause the current operation, async function, plain function, or operation function.
* The calling operation will be resumed (or errored) once call is completed.
*
* `call()` is a uniform integration point for calling async functions,
* evaluating promises, generator functions, operations, and plain
* functions.
*
* It can be used to treat a promise as an operation:
*
* @example
* ```javascript
* let response = yield* call(fetch('https://google.com'));
* ```
* generator functions, and plain functions.
*
* or an async function:
* To call an async function:
*
* @example
* ```typescript
Expand All @@ -59,128 +50,52 @@ export type Callable<T> =
* }
* ```
*
* It can be used to run an operation in a separate scope to ensure that any
* resources allocated will be cleaned up:
*
* @example
* ```javascript
* yield* call(function*() {
* let socket = yield* useSocket();
* return yield* socket.read();
* }); // => socket is destroyed before returning
* ```
*
* It can be used to run a plain function:
* or a plain function:
*
* @example
* ```javascript
* yield* call(() => "a string");
* ```
*
* Because `call()` runs within its own {@link Scope}, it can also be used to
* establish [error boundaries](https://frontside.com/effection/docs/errors).
*
* @example
* ```javascript
* function* myop() {
* let task = yield* spawn(function*() {
* throw new Error("boom!");
* });
* yield* task;
* }
*
* function* runner() {
* try {
* yield* myop();
* } catch (err) {
* // this will never get hit!
* }
* }
*
* function* runner() {
* try {
* yield* call(myop);
* } catch(err) {
* // properly catches `spawn` errors!
* }
* }
* ```
*
* @param callable the operation, promise, async function, generator funnction, or plain function to call as part of this operation
* @param callable the operation, promise, async function, generator funnction,
* or plain function to call as part of this operation
*/
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: Operation<T>): Operation<T>;
export function call<T>(callable: Promise<T>): Operation<T>;
export function call<T>(callable: Callable<T>): Operation<T> {
return action(function* (resolve, reject) {
try {
if (typeof callable === "function") {
let fn = callable as () => Operation<T> | Promise<T> | T;
resolve(yield* toop(fn()));
} else {
resolve(yield* toop(callable));
}
} catch (error) {
reject(error);
}
});
}

function toop<T>(
op: Operation<T> | Promise<T> | T,
export function call<T, TArgs extends unknown[] = []>(
callable: Callable<T, TArgs>,
...args: TArgs
): Operation<T> {
if (isPromise(op)) {
return expect(op);
} else if (isIterable(op)) {
let iter = op[Symbol.iterator]();
if (isInstructionIterator<T>(iter)) {
// operation
return op;
} else {
// We are assuming that if an iterator does *not* have `.throw` then
// it must be a built-in iterator and we should return the value as-is.
return bare(op as T);
}
} else {
return bare(op as T);
}
}

function bare<T>(val: T): Operation<T> {
return {
[Symbol.iterator]() {
return { next: () => ({ done: true, value: val }) };
let target = callable.call(void (0), ...args);
if (
typeof target === "string" || Array.isArray(target) ||
target instanceof Map || target instanceof Set
) {
return constant(target)[Symbol.iterator]();
} else if (isPromise<T>(target)) {
return action<T>(function wait(resolve, reject) {
target.then(resolve, reject);
return () => {};
}, `async call ${callable.name}()`)[Symbol.iterator]();
} else if (isOperation<T>(target)) {
return target[Symbol.iterator]();
} else {
return constant(target)[Symbol.iterator]();
}
},
};
}
1;

function expect<T>(promise: Promise<T>): Operation<T> {
return pause((resolve, reject) => {
promise.then(resolve, reject);
return () => {};
});
}

function isFunc(f: unknown): f is (...args: unknown[]) => unknown {
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 isPromise<T>(
target: Operation<T> | Promise<T> | T,
): target is Promise<T> {
return target && typeof (target as Promise<T>).then === "function";
}

function isIterable<T>(it: unknown): it is Iterable<T> {
if (!it) return false;
return typeof (it as Iterable<T>)[Symbol.iterator] === "function";
function isOperation<T>(
target: Operation<T> | Promise<T> | T,
): target is Operation<T> {
return target &&
typeof (target as Operation<T>)[Symbol.iterator] === "function";
}
25 changes: 25 additions & 0 deletions lib/callcc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { lift } from "./lift.ts";
import { Err, Ok, Result, unbox } from "./result.ts";
import { Operation } from "./types.ts";
import { withResolvers } from "./with-resolvers.ts";
import { spawn } from "./spawn.ts";
import { encapsulate } from "./task.ts";

export function* callcc<T>(
op: (
resolve: (value: T) => Operation<void>,
reject: (error: Error) => Operation<void>,
) => Operation<void>,
): Operation<T> {
let result = withResolvers<Result<T>>();

let resolve = lift((value: T) => result.resolve(Ok(value)));

let reject = lift((error: Error) => result.resolve(Err(error)));

return yield* encapsulate(function* () {
yield* spawn(() => op(resolve, reject));

return unbox(yield* result.operation);
});
}
7 changes: 7 additions & 0 deletions lib/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Operation } from "./types.ts";

export function constant<T>(value: T): Operation<T> {
return {
[Symbol.iterator]: () => ({ next: () => ({ done: true, value }) }),
};
}
Loading

0 comments on commit 8be4415

Please sign in to comment.