{"id":18453348,"url":"https://github.com/quasarbright/conteffects","last_synced_at":"2025-04-22T11:56:44.858Z","repository":{"id":92838814,"uuid":"359537877","full_name":"quasarbright/ContEffects","owner":"quasarbright","description":null,"archived":false,"fork":false,"pushed_at":"2021-04-23T01:52:04.000Z","size":13,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-16T14:26:43.116Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/quasarbright.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","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":"2021-04-19T17:09:01.000Z","updated_at":"2021-04-23T01:52:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"84d465ef-f9a7-4ee7-95af-e174b269dbda","html_url":"https://github.com/quasarbright/ContEffects","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/quasarbright%2FContEffects","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quasarbright%2FContEffects/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quasarbright%2FContEffects/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quasarbright%2FContEffects/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/quasarbright","download_url":"https://codeload.github.com/quasarbright/ContEffects/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250237825,"owners_count":21397400,"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":[],"created_at":"2024-11-06T07:35:54.724Z","updated_at":"2025-04-22T11:56:44.835Z","avatar_url":"https://github.com/quasarbright.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ContEffects\n\nA tiny programming language with algebraic effects. It was named ContEffects because its implementation was inspired by [the `Cont` monad](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Cont.html). I thought I'd end up using that monad to implement effect handlers, similar to the way I usually use `ExceptT` to implement throwing and catching exceptions in an interpreter, but I ended up just needing `ReaderT`.\n\nAlgebraic effects are like exceptions that you can resume from. Rather than \"throwing\" and \"catching\" an exception and continuing from where you caught it, you \"perform\" and \"handle\" an effect and resume at the place where it was \"performed\"\n\nEffects are implemented as implicitly passed callbacks\n\n```\ntry (1 + 2 + perform 5)\nhandle e (e + e)\n```\nwill evaluate to 12\n\nIn this example, 5 is an effect, and the handler is `e -\u003e e + e`. This is a little silly, since usually effects are structured objects with information, but having effects be any old value is enough to demonstrate the mechanism. In a more sophisticated language, effects would have their own special values and you'd handle specific exceptions like this:\n\n```\ntry (...)\nhandle ThisEffect as e (...)\nhandle ThatEffect as e(...)\n```\n\nThese effects would also have data in them. A real-world example would be running an HTTP request:\n\n```\nfunction go() {\n  ...\n  let json = perform Fetch(\"https://www.some.url.com/endpoint\") in\n  ...\n}\n```\n\nYou could handle a `Fetch` effect by really running an HTTP request, or you could return some dummy data if you want to test this function:\n\n```\ntry( go() ) handle Fetch as f ( {\"name\": \"big chungus\"} )\n```\n\nEffects bubble up to the nearest handler. When the handler's body evaluates, the `perform` evaluates to that value and the computation continues. Handlers themselves can perform effects, and an outer handler can handle the effect, allowing the inner handler to resume and provide the initial `perform` with a value. For example,\n\n```\ntry (\n  try (1 + perform 2)\n  handle e (e + perform 3)\n) handle e (e + e)\n```\nwill evaluate to 9\n\nIf an effect is unhandled and reaches the top-level, the program crashes.\n\n`perform` can be used outside of a `try`:\n\n```\nlet go = \\x -\u003e 1 + perform x in\ntry( go(3) )\nhandle e (e + e)\n```\n\nwill evaluate to 7\n\n# how it works\n\nI use the reader monad with an environment that contains a mapping of variable names to values and the current handler:\n\n```haskell\ndata Env = Env { _vars :: Map String Value, _handler :: Value }\n\ndata Value = VInt Int | VUnit | VLambda (Map String Value) (Maybe Value) String Expr | VBuiltinFun (Value -\u003e Interpreter Value)\n```\n\nin a try/handle expression, I just convert the `handle e (...)` to a `VLambda` and evaluate the try expression using that lambda as the handler. There needs to be some special care for ensuring the handler and try code use the correct `Env`s, but that's the gist of how it works. Here are the gotchas:\n* a handler's lambda must close over the variables that were in scope before the try and not use the environment in which the effect was performed\n* when running a handler's code, the \"current handler\" in its env must be the handler of the whole try/handle, that way handlers can perform effects too\n* a lambda should be able to perform effects and then be ran in a variety of handlers. So the behavior of `perform` has to be dynamic in the same way that an exception thrown in a function can be caught a variety of ways depending on how the caller catches it\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquasarbright%2Fconteffects","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquasarbright%2Fconteffects","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquasarbright%2Fconteffects/lists"}