{"id":15823440,"url":"https://github.com/3shain/algebraic-effects-ts","last_synced_at":"2025-04-01T16:30:45.381Z","repository":{"id":128410256,"uuid":"497101841","full_name":"3Shain/algebraic-effects-ts","owner":"3Shain","description":"Multi-shot CPS typed algebraic effects and handlers, in typescript.","archived":false,"fork":false,"pushed_at":"2023-09-11T11:31:09.000Z","size":26,"stargazers_count":17,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-28T15:54:37.834Z","etag":null,"topics":["algebraic-effect-handlers","algebraic-effects","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/3Shain.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-27T18:37:49.000Z","updated_at":"2025-03-09T11:38:30.000Z","dependencies_parsed_at":null,"dependency_job_id":"1e67fd29-87f0-430a-8ae2-75f0ebcf0345","html_url":"https://github.com/3Shain/algebraic-effects-ts","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3Shain%2Falgebraic-effects-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3Shain%2Falgebraic-effects-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3Shain%2Falgebraic-effects-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3Shain%2Falgebraic-effects-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/3Shain","download_url":"https://codeload.github.com/3Shain/algebraic-effects-ts/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246620304,"owners_count":20806745,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algebraic-effect-handlers","algebraic-effects","typescript"],"created_at":"2024-10-05T08:10:28.851Z","updated_at":"2025-04-01T16:30:45.064Z","avatar_url":"https://github.com/3Shain.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# algebraic-effects-ts\n\nThis project implements _Algebraic Effects and Handlers_ described in [this paper](https://www.eff-lang.org/handlers-tutorial.pdf) in TypeScript with (almost) fully type system support.\n\n## Highlights\n\n- **Fully typed**: Leverage TypeScript's type system to trace your computational effects.\n- **Multi-shot**: Not implemented by generator but continuations such that an effect can continue multiple times with different payloads.\n- **Simple**: Not a surprise\n  - Bonus: compare the .js file and .d.ts declaration. You'll realize they are in fact isomorphic.\n\n\nHowever this project is __not ready for production__, as there is a limitation of typescript recursive type inference (not safe to be abused), as well as the dev experience with continuation(callbacks) is subjectively poor :/\n\n## How it works\n\nThe js implementation is almost a translation of operational semantics from the paper mentioned above and the type declaration is naturally an isomorphism to the js one.\n\n\u003e To get a sense of the following contents, I'm assuming that you know about algebraic effects already (at least you have read some related papers).\n\nThere are 6 types of computation and 2 of those are primitive: Others can be transformed to primitives or already provided by javascript.\n\n- return(value) : A computation returns a value\n- op(effect, payload, continuation) : A computation performs effect with payload, then evaluate continuation with the result of effect.\n- sequence(computation, continuation): Run computation then evaluate continuation with the result of computation. Can be transformed to either Return of Op\n- with(handler).handle(computation): Handle the operation yielded by computation if capable or yield forward. Can be transformed to either Return or Op\n- if value then computation1 else computation2: Map to ternary operator `value ? computation1 : computation2`\n- fn(value): Map to normal function calls\n\nAnd computation from the perspective of regular javascript semantic is in fact a plain object of either `{type: 'ret', value}` or `{type: 'op', effect, payload, continuation }`. (can be created by calling `ret(value)` or `op(effect,payload)`)\n\n\u003e Why not `op(effect,payload, continuation)` ? Well it should be but for convenience a wrapped function is provided (_generic effects_, see the paper for extra explaining)\n\n## Example\n\n```ts\nimport { run, op, ret, seq } from \"algebraic-effects-ts\";\n\nconst print = (str: string) =\u003e op(\"io\", () =\u003e console.log(str));\n\nrun(seq(ret(\"Hello, \"), (a) =\u003e seq(ret(\"world!\"), (b) =\u003e print(a + b))));\n\n// expect console output: Hello, world!\n```\n\nCheck out the tests inside `/tests` folder.\n\n## Effects\n\nSimply literal string is used to represent effect types because it's enough and typescript has no first-class support of nominal typing (could be done via literal string or unique symbol but the latter is not human distinguishable)\n\nThe payload and return type of effects are tightly coupled with effect declaration and you should declare like this\n\n```ts\ndeclare module 'algebraic-effects-ts/effects' {\n  interface Effects\u003cT\u003e {\n    // what is the T for? in case you want HKT. but you may not need it.\n    // you can always pass it by the second generic type parameter of `op` \n    effectName: Effect\u003cPayloadType, ResumeType\u003e;\n    ...\n  }\n}\n```\n\nand `op(\"effectName\", ...)` will give you correct payload (the second parameter) and resume type (the computation result type) inference.\n\n### Built-in effects\n\n#### `op(\"io\" , ()=\u003e \u003c...\u003e)`\n\nRun a side effect. Then resume with the return value.\n\n#### `op(\"fail\", undefined)`\n\nJust terminate and throw an js error.\n\n## Handlers\n\nTo define a handler, first you need to define a class with two generic type parameter\n\n```ts\nclass MyHandler\u003cT,K\u003e {\n  // T is for return type of handled computation\n  // K is for continuation (which is a computation!)\n\n  // this is optional, if you don't modify the return value\n  // NB: you should always return a \"computation\" in js functions instead of arbitrary js value.\n  return(value: T) {\n    return ret(value); \n  }\n\n  // assume myEffect is already declared and \n  // `MyEffectPayloadType` as well as `MyEffectResumeType` are defined somewhere\n  myEffect(payload: MyEffectPayloadType, k: (r:MyEffectResumeType) =\u003e K) {\n    // ... effect implementation\n    return k(doSomethingWith(payload));\n    // technically speaking this example is identical to just call a function... not a very good demonstration\n  }\n}\n```\n\nthen define a HKT\n```ts\nimport type { HKT } from 'algebraic-effects-ts';\n\ninterface MyHandlerHKT extends HKT {\n   readonly type: MyHandler\u003c\n        this[\"computationReturn\"],\n        this[\"continuation\"]\n      \u003e;\n}\n```\n\nthis is how you apply your handler\n\n```ts\nwithHandler\u003cMyHandlerHKT\u003e(new MyHandler()).handle(/* computation to be handled */);\n// and it returns another computation\n```\n\n### Side notes\n\nIf you are familiar with `fp-ts` then you may recognize that there are two kinds of HKT Encoding methods be used (one for effect and another for handler). I was trying to unify them but I failed. I think current result is acceptable. The first encoding (fp-ts style) is global, while it's not flexible but it's also reasonable to make effects global unique. The second (idea from [this post](https://dev.to/matechs/encoding-of-hkts-in-typescript-5c3)) for handler one is more verbose but flexible so that defining a local handler becomes possible. And it makes generic parameter passing easier (you can check the backtrack example inside tests: we have nested handler and the inside one needs an extra generic type parameter from the outside one). The verbosity is not a big concern as we can always hide them via encapsulation.\n\n## Type\n\nA fun fact is that all performed effects are encoded inside the type of computation, as well as the computation result type. For example: `Operation\u003c\"print\", Operation\u003c\"print\", Operation\u003c\"print\", Return\u003cvoid\u003e\u003e\u003e\u003e` indicates a computation performs `print` 3 times and return no value. If there are branches, you will get type union.\n\nThere is a util type `ComputationResult\u003cTComputation\u003e` that gives you the final result of computation. And `ToHandleEffects\u003cTComputation\u003e` provide all the possible effects as a literal string union. The entry point function `run` utilize these type :\n```ts\ndeclare function run\u003cT extends Computation\u003e(\n  cc: T\n): ToHandleEffects\u003cT\u003e extends \"io\" | \"fail\" ? ComputationResult\u003cT\u003e : `unhanded effect: ${Exclude\u003cToHandleEffects\u003cT\u003e, \"io\" | \"fail\"\u003e}`;\n```\nto guarantee that all effects except for built-ins are properly handled, otherwise you will get literal type `unhanded effect: {effect type}` in compile-time and it reflects an error will be thrown in runtime.\n\nNote typescript doesn't support recursive function inference, this is very inconvenient because you need to declare the computation type manually but it's always intended to be inferred implicitly (see backtrace example inside the tests). \n\n## References\n\n[the paper](https://www.eff-lang.org/handlers-tutorial.pdf)\n\n[idea of HKT Encoding](https://dev.to/matechs/encoding-of-hkts-in-typescript-5c3)\n\n## Road map\n\n- [x] async, concurrenc\n  - However I doubt it's leaky for async to be defined as a user handler (see comments in the async test case). It's more plausible to make it a built-in effect like `io`. Need more evidence.\n- [ ] (PoC) a language for algebraic effects that use typescript as kernel language (simple ast transformation?), or a language extension?\n\n## Author\n\n3Shain\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F3shain%2Falgebraic-effects-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F3shain%2Falgebraic-effects-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F3shain%2Falgebraic-effects-ts/lists"}