Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/susisu/effectful
Type-safe effects & handlers
https://github.com/susisu/effectful
Last synced: 28 days ago
JSON representation
Type-safe effects & handlers
- Host: GitHub
- URL: https://github.com/susisu/effectful
- Owner: susisu
- License: mit
- Created: 2022-10-23T07:19:02.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2023-03-17T02:47:12.000Z (over 1 year ago)
- Last Synced: 2024-05-17T12:03:37.107Z (6 months ago)
- Language: TypeScript
- Homepage:
- Size: 66.4 KB
- Stars: 5
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# @susisu/effectful
[![CI](https://github.com/susisu/effectful/actions/workflows/ci.yml/badge.svg)](https://github.com/susisu/effectful/actions/workflows/ci.yml)
``` shell
# npm
npm i @susisu/effectful
# yarn
yarn add @susisu/effectful
# pnpm
pnpm add @susisu/effectful
```## Example
``` ts
// 1. Register effects by augmenting `EffectRegistry`.declare module "@susisu/effectful" {
interface EffectRegistry {
// Reads contents of a file
read: {
// Constrains `T` = `string`
constraint: (x: string) => T;
filename: string;
};
// Prints a message
print: {
constraint: (x: void) => T;
message: string;
};
// Waits for a promise
async: {
promise: Promise;
};
}
}// 2. Define effect constructors (more accurately, atomic computations) for convenience.
// `Eff` is the type of a compuation that performs effects in `Row` and returns `T`.import type { Eff } from "@susisu/effectful";
import { perform } from "@susisu/effectful";function read(filename: string): Eff<"read", string> {
return perform({
// Property name in `EffectRegistry`
id: "read",
// Property type in `EffectRegistry`
data: {
// `constraint` should be an identity function
constraint: (x) => x,
filename,
},
});
}function print(message: string): Eff<"print", void> {
return perform({
id: "print",
data: {
constraint: (x) => x,
message,
},
});
}function async(promise: Promise): Eff<"async", T> {
return perform({
id: "async",
data: {
promise,
},
});
}// 3. Write complex computations using generators.
function* getSize(filename: string): Eff<"read", number> {
// Use `yield*` to perform effects
const contents = yield* read(filename);
return contents.length;
}function* main(): Eff<"read" | "print", void> {
// `yield*` can also be used to compose computations
const size = yield* getSize("./input.txt");
yield* print(`The file contains ${size} characters!`);
}// 4. Write interpreters.
import type { EffectId } from "@susisu/effectful";
import { interpret, run } from "@susisu/effectful";
import { readFile } from "fs/promises";function interpretRead(comp: Eff): Eff {
return interpret<"read", Row | "async", T>(comp, {
*read(eff, resume) {
const contents = yield* async(readFile(eff.data.filename, "utf-8"));
// Use `constraint` to pass `contents` (which is a `string`) to `resume` (which takes a value of type `T`)
return yield* resume(eff.data.constraint(contents));
},
});
}function interpretPrint(comp: Eff): Eff {
return interpret<"print", Row, T>(comp, {
*print(eff, resume) {
console.log(eff.data.message);
return yield* resume(eff.data.constraint(undefined));
},
});
}function runAsync(comp: Eff<"async", T>): Promise {
return run(comp, (x) => Promise.resolve(x), {
async(eff, resume) {
return eff.data.promise.then(resume);
},
});
}// 5. Run computations.
runAsync(interpretPrint(interpretRead(main()))).catch((err) => {
console.error(err);
});
```## License
[MIT License](http://opensource.org/licenses/mit-license.php)
## Author
Susisu ([GitHub](https://github.com/susisu), [Twitter](https://twitter.com/susisu2413))
## Prior art
- [briancavalier/fx-ts](https://github.com/briancavalier/fx-ts/)
- [susisu/effects](https://github.com/susisu/effects)