{"id":13719356,"url":"https://github.com/koka-lang/libmprompt","last_synced_at":"2025-09-11T21:37:10.790Z","repository":{"id":43016382,"uuid":"351505111","full_name":"koka-lang/libmprompt","owner":"koka-lang","description":"Robust multi-prompt delimited control and effect handlers in C/C++","archived":false,"fork":false,"pushed_at":"2023-11-28T18:07:18.000Z","size":2405,"stargazers_count":115,"open_issues_count":7,"forks_count":13,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-03-26T00:40:30.263Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","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/koka-lang.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}},"created_at":"2021-03-25T16:33:01.000Z","updated_at":"2025-02-16T12:05:35.000Z","dependencies_parsed_at":"2024-02-03T08:44:31.390Z","dependency_job_id":null,"html_url":"https://github.com/koka-lang/libmprompt","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koka-lang%2Flibmprompt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koka-lang%2Flibmprompt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koka-lang%2Flibmprompt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koka-lang%2Flibmprompt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/koka-lang","download_url":"https://codeload.github.com/koka-lang/libmprompt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248519546,"owners_count":21117761,"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-08-03T01:00:47.008Z","updated_at":"2025-04-12T05:10:30.166Z","avatar_url":"https://github.com/koka-lang.png","language":"C","funding_links":[],"categories":["C"],"sub_categories":[],"readme":"\u003cimg align=\"left\" width=\"100\" height=\"100\" src=\"doc/libmprompt-logo.png\"/\u003e\n\n[\u003cimg style=\"float:right\" align=\"right\" src=\"https://dev.azure.com/Daan0324/libmprompt/_apis/build/status/koka-lang.libmprompt?branchName=main\"/\u003e](https://dev.azure.com/Daan0324/libmprompt/_build?definitionId=2)\n\n# libmprompt\n\n_Note: The library is under development and not yet complete. This library should not be used in production code._  \nLatest release: v0.6.3, 2021-06-18.\n\nA 64-bit C/C++ library that aims to implement robust and efficient multi-prompt delimited control. \n\nThe implementation is based on _in-place_ growable light-weight gstacks (called `gstack`s) which use \nvirtual memory to enable growing the gstack (up to 8MiB) but start out using \njust 4KiB of committed memory. The library is only available \nfor 64-bit systems (currently Windows, Linux, macOS, and various BSD's) \nas smaller systems do not have enough virtual address space.\n\nThere are two libraries provided:\n\n- `libmprompt`: the primitive library that provides multi-prompt delimited control. \n  The multi-prompt abstraction has well-defined semantics and is the\n  minimal control abstraction that can be typed with simple types.\n  As such, we view the `libmprompt` library as a good API that should be provided\n  by the OS or language runtime to provide generic and sound delimited control.\n  `libmpromptx` is the C++ compiled variant that integrates exception handling\n  where exceptions are propagated correctly through the gstacks.\n  \n- `libmpeff`: a small example library that uses `libmprompt` to implement\n  efficient algebraic effect handlers (with a similar interface as [libhandler]).\n  This is an easier abstraction to program with using multi-prompts directly.\n\nParticular aspects:\n\n- The goal is to be fully compatible with C/C++ semantics and to be able to\n  link to this library and use the multi-prompt abstraction _as is_ without special\n  considerations for signals, stack addresses, unwinding etc. \n  In particular, this library has _address stability_: using the in-place \n  growable gstacks (through virtual memory), these stacks are never moved, which ensures \n  addresses to the stack are always valid (in their lexical scope) -- to the C program\n  it still looks as if there is just one large stack. There is\n  also no special function prologue/epilogue needed as with [split stacks][split]\n  for example.\n  \n- The multi-prompt abstraction has a precise [semantics] and is well-typed. \n  Moreover, it guarantees there is always just one logical active stack (as a chain of\n  gstacks) which allows exceptions to propagate naturally and also provides\n  natural [backtraces] for any resumed prompt.\n\n- A drawback of our implementation is that it requires 64-bit systems in order to have enough\n  virtual address space. Moreover, at miminum 4KiB of memory is committed per \n  (active) prompt. On systems without \"overcommit\" we use internal _gpools_ to \n  still be able to commit stack space on-demand using a special signal handler.\n  (as an aside, using this on 32-bit systems is not entirely out of reach -- if one\n   reserves 2GiB virtual memory for the stacks, we could have ~16000 64KiB stacks \n   which may well be enough for almost any application).   \n\n- We aim to support millions of prompts with fast yielding and resuming. If we run\n  the [`mp_async_test1M`](test/main.c#L82) test we simulate an asynchronous\n  service which creates a fresh prompt on each \"connection\", enters it and then suspends it\n  (simulating waiting for an async result).\n  Later it is resumed again where it calls a function that consumes 32KiB stack space, \n  and finally returns. The test simulates 10 million connections with 10000 suspended \n  prompts at any time:\n  ```\n  run 10M connections with 10000 active at a time, each using 32kb stack...\n  total stack used: 312500.000mb, count=10000000\n  elapsed: 0.932s, user: 0.883s, sys: 0.049s, rss: 42mb, main rss: 39mb\n  ```\n  This is about 10M \"connections\" per second (single threaded, Ubuntu 20, AMD5950X),\n  where each connection creates a fresh prompt and context switches 4 times.\n\n\nEnjoy,\n  Daan Leijen and KC Sivaramakrishnan.\n\nReleases:\n- 2021-06-18: `v0.6.3`: support for apple M1.\n- 2021-06-06: `v0.6.2`: fix abort yields, add triples test.\n- 2021-05-06: `v0.6.1`: build fixes for freeBSD.\n- 2021-04-16: `v0.6`  : improved security further, random initialization of the jump guard.\n- 2021-04-13: `v0.5.4`: improved security against stack buffer overflow, simplify creation of multi-shot resumptions.\n- 2021-04-08: `v0.5.2`: better handling of signals, improved Windows page fault handler.\n- 2021-04-04: `v0.4`: initial support for Linux arm64.\n- 2021-04-03: `v0.3`: better backtraces on Windows, support libunwind.\n- 2021-04-01: `v0.2`: improved debugging on macOS with `lldb`.\n- 2021-03-30: `v0.1`: initial release.\n\n\n[split]: https://gcc.gnu.org/wiki/SplitStacks\n[libhandler]: https://github.com/koka-lang/libhandler\n[backtraces]: #backtraces\n[semantics]: #semantics\n\n# Building\n\nTested on Linux (x64 and arm64), macOS (x64, arm64), Windows (x64), and freeBSD (x64).\n\n## Linux and macOS\n\nWe use `cmake` to build:\n\n```\n\u003e mkdir -p out/debug    # or out/release\n\u003e cd out/debug\n\u003e cmake ../..\n\u003e make\n\u003e ctest .\n```\n\nThis will build the libraries `libmpromptx.a` and `libmpeffx.a`, and run the tests.\n\nPass the option `cmake ../.. -DMP_USE_C=ON` to build the C versions of the libraries\n(but these do not handle- or propagate exceptions).\n\n## Windows\n\nWe use Visual Studio 2019 to develop the library -- open the solution \nin `ide/vs2019/libmprompt.sln` to build and test.\n\n## Issues\n\nSome known issues are:\n\n- `gdb`, `lldb`: when debugging on Linux (without overcommit) you may see \n  segmentation fault errors (`SEGV`) which happen when demand paging stack memory; \n  you need to continue through those or set the debugger to ignore them \n  (enter `handle SIGSEGV nostop noprint` in `gdb`).\n  Another workaround is to use overcommit on Linux when debugging which does not use\n  a signal handler:\n  ```C\n  mp_config_t cfg = mp_config_default(); cfg.stack_use_overcommit = true; mp_init(\u0026cfg); \n  ```\n  \n- `lldb`: when debugging on macOS we use an extra thread\n  to handle Mach exceptions (to avoid a long standing [bug](https://bugs.llvm.org//show_bug.cgi?id=22868) in `lldb`).\n  \n- Debug stack traces in Visual Studio (and `windbg`) work well most of the time, but sometimes the debugger stops \n  a backtrace too soon when libmprompt is unable to put a gstack at a lower address than its parent.\n\n\n# Libmprompt C Interface\n\n```C\n// Types\ntypedef struct mp_prompt_s  mp_prompt_t;     // resumable \"prompts\"\ntypedef struct mp_resume_s  mp_resume_t;     // abstract resumption\n\n// Function types\ntypedef void* (mp_start_fun_t)(mp_prompt_t*, void* arg); \ntypedef void* (mp_yield_fun_t)(mp_resume_t*, void* arg);  \n\n// Continue with `fun(p,arg)` under a fresh prompt `p`.\nvoid* mp_prompt(mp_start_fun_t* fun, void* arg);\n\n// Yield back up to a parent prompt `p` and run `fun(r,arg)` \nvoid* mp_yield(mp_prompt_t* p, mp_yield_fun_t* fun, void* arg);\n\n// Resume back to the yield point with a result; can be used at most once.\nvoid* mp_resume(mp_resume_t* resume, void* arg);\nvoid* mp_resume_tail(mp_resume_t* resume, void* arg);\nvoid  mp_resume_drop(mp_resume_t* resume);\n```\n\n```C\n// Multi-shot resumptions; use with care in combination with linear resources.\nmp_resume_t* mp_resume_multi(mp_resume_t* r); // create a fresh multi-shot resumption\nmp_resume_t* mp_resume_dup(mp_resume_t* r);   // increase ref-count on a multi-shot resumption\n\n// Portable backtrace\nint mp_backtrace(void** backtrace, int len);\n```\n\n\n## Backtraces\n\nA nice property of muli-prompts is that there is always\na single strand of execution, together with suspended prompts.\nIn contrast to lower level abstractions, like fibers, there is no \narbitrary switching between stacks: one can only yield up to a\nparent prompt (capturing all gstacks up to that prompt) or \nresume a suspended prompt chain (and restoring all gstacks in that context).\nAs a consequence, the active chain of prompts always form a logical stack \nand we can have natural propagation of exceptions with proper backtraces.\n\nHere is an example of a backtrace on Linux:\n\n\u003cimg src=\"doc/backtrace2.jpg\" width=600px\u003e\n\nHere a breakpoint was set in code that was resumed\nwhere the backtrace continues into the main stack. This is quite nice\nfor debugging compared to callback based programming for example.\n\nHere is a backtrace in the Visual Studio debugger:\n\n\u003cimg src=\"doc/backtrace3.jpg\" width=600px\u003e\n\n(Unfortunately, on Windows, in rare cases a backtrace can still be cut short \nwhen libmprompt is unable to place a gstack at a lower address as its parent.)\n\n\n\n## Semantics\n\nThe semantics of delimited multi-prompt control \ncan be described precisely:\n\nSyntax:\n```ioke\ne ::= v              ; value\n   |  e e            ; application\n   |  yield m v      ; yield to a prompt identified by marker `m`\n   |  prompt v       ; start a new prompt (passing its marker to `v`)\n   |  @prompt m e    ; internal: a prompt frame identified by marker `m`\n\nv ::= x              ; variables\n   |  \\x. e          ; function with parameter `x` (lambda expression)\n   |  ...            ; integer constants, primitives (e.g. addition), etc.\n```\n\nEvaluation context:\n```ioke\nE ::= []            ; hole\n   |  E e           ; evaluate function first\n   |  v E           ; and then the argument \n   |  @prompt m E   ; we can evaluate under a prompt frame\n```\nAn evaluation context is an expression with a hole at the current point in the \nevalution. It essentially describes the stack+registers where the hole is the current instruction pointer.\nWe can apply a context to an expression using square bracets `E[x]`; \nfor example, `(@prompt m (f (g [])))[x]` becomes `@prompt m (f (g x))`.\n\nOperational semantics:\n```ioke\n              e ----\u003e e'\n(STEP)    -----------------\n          E[e] |----\u003e E[e']    \n```\n\nWe can now keep evaluating inside an expression context using small step transitions:\n```ioke\n(APP)      (\\x. e) v                ----\u003e  e[x := v]                ; beta-rule, application\n(PROMPT)   prompt v                 ----\u003e  @prompt m (v m)          ; install a new prompt with a fresh marker `m`\n(RETURN)   @prompt m v              ----\u003e  v                        ; returning a result discards the prompt frame\n(YIELD)    @prompt m E[yield m f]   ----\u003e  f (\\x. @prompt m E[x])   ; yield to prompt frame `m`, capturing context `E`\n```\n\nNote how in `(YIELD)` we yield with a function `f` to a prompt `m`. This\ncontinues with executing `f` (popping the prompt) but with the argument\n`\\x. @prompt m E[x]` which is the resumption function: calling it will\nrestore the prompt and the original execution\ncontext `E` (!), and resume execution at the original yield location.\nFor example:\n\n```ioke\n       prompt (\\x. 1 + yield x (\\k. k 41))            \n|----\u003e @prompt m ((\\x. 1 + yield x (\\k. k 41)) m)     ; fresh marker `m`\n|----\u003e @prompt m (1 + yield m (\\k. k 41))             ; note: `\\k. k 41` is the function that is yielded up\n==     @prompt m ((1 + [])[yield m (\\k. k 41)])       ; yield back up to `m`, capturing E\n|----\u003e (\\k. k 41) (\\x. @prompt m (1 + [])[x])         ; continue with the function of the yield\n|----\u003e (\\x. @prompt m (1 + [])[x]) 41                 ; resume by applying `k`\n|----\u003e @prompt m ((1 + [])[41])                       ; resumed to the yield with result 41\n==     @prompt m (1 + 41)\n|----\u003e @prompt m 42\n|----\u003e 42\n```\n\nIn the C implementation, the unique markers `m` are simply\nrepresented directly by a `mp_prompt_t*`.\nAt runtime, yielding to a prompt that is not an ancestor (i.e. no\nlonger in scope), is an error -- just like an unhandled exception.\n(Effect type systems, like in [Koka], can prevent this situation\nstatically at compile-time but in our library this is a runtime error).\n\nThese primitives are very expressive but can still be\ntyped in in simply typed lambda calculus, and are sound and\ncomposable:\n```haskell\nprompt :: (Marker a -\u003e a) -\u003e a               \nyield  :: Marker a -\u003e ((b -\u003e a) -\u003e a) -\u003e b   \n```\n\nThe action given to `prompt` gets a marker that has \na type `a`, corresponding to the type of the current context `a` (the _answer_ type).\nWhen yielding to a marker of type `a`, the yielded function has type `(b -\u003e a) -\u003e a`,\nand must return results of type `a` (corresponding to the marker context).\nMeanwhile, the passed in resumption function `(b -\u003e a)` expects an argument\nof type `b` to resume back to the yield point. Such simple types cannot be \ngiven for example to any of `shift`/`reset`, `call/cc`, fibers, or co-routines, \nwhich is one aspect why we believe multi-prompt delimited control is preferable.\n\nThe growable gstacks are used to make capturing- and resuming\nevaluation contexts efficient. Each `@prompt m` frame sits\non the top a gstack from which it can yield and resume \nin constant time. This can for example be used to create\ngreen thread scheduling, exceptions, iterators, async-await, etc.\n\nFor a more in-depth explanation of multi-prompt delimited control,\nsee \"_Evidence Passing Semantics for Effect Handler_\", Ningning Xie and Daan Leijen, MSR-TR-2021-5 \n([pdf](https://www.microsoft.com/en-us/research/publication/generalized-evidence-passing-for-effect-handlers/)).\n\n\n## An implementation based on in-place growable stacks\n\nEach prompt starts a growable gstack and executes from there.\nFor example, we can have:\n```ioke\n(gstack 1)              (gstack 2)              (gstack 3)\n\n|-------------|\n| @prompt A   |\n|-------------|\n|             |\n| ...         |\n| prompt \u003c------------\u003e |-------------|\n|             |         | @prompt B   | \n.             .         |-------------|\n.             .         |             |\n.             .         | ...         |         \n                        | prompt \u003c------------\u003e |------------|\n                        .             .         | @prompt C  |\n                        .             .         |------------|\n                        .             .         | 1+         |\n                                                | yield B f  |\u003c\u003c\u003c\n                                                .            .\n                                                .            .\n```   \nwhere `\u003c\u003c\u003c` is the currently executing statement.\n\nThe `yield B f` can yield directly to prompt `B` by\njust switching stacks. The resumption `r` is also\njust captured as a pointer and execution continues\nwith `f(r)`: (rule `(YIELD)` with `r` = `\\x. @prompt B(... @prompt C. 1+[])[x]`)\n```ioke\n(gstack 1)              (gstack 2)              (gstack 3)\n\n|-------------|\n| @prompt A   |\n|-------------|\n|             |\n| ...         |         (suspended)\n| resume_t* r ~~~~~~~~\u003e |-------------|\n| f(r)        |\u003c\u003c\u003c      | @prompt B   | \n.             .         |-------------|\n.             .         |             |\n.             .         | ...         |         \n                        | prompt \u003c------------\u003e |------------|\n                        .             .         | @prompt C  |\n                        .             .         |------------|\n                        .             .         | 1+         |\n                                                | []         |\n                                                .            .\n                                                .            .\n```   \n\nLater we may want to resume the resumption `r` again with\nthe result `42`: (`r(42)`)\n\n```ioke\n(gstack 1)              (gstack 2)              (gstack 3)\n\n|-------------|\n| @prompt A   |\n|-------------|\n|             |\n| ...         |         (suspended)\n| resume_t* r ~~~~~~~~\u003e |-------------|\n|             |         | @prompt B   | \n| ...         |         |-------------|\n| resume(r,42)|\u003c\u003c\u003c      |             |\n.             .         | ...         |         \n.             .         | prompt \u003c------------\u003e |------------|\n.             .         .             .         | @prompt C  |\n                        .             .         |------------|\n                        .             .         | 1+         |\n                                                | []         |\n                                                .            .\n```   \nNote how we grew the gstack 1 without moving gstack 2 and 3.\nIf we have just one stack, an implementation needs to copy \nand restore fragments of the stack (which is what [libhandler] does),\nbut that leads to trouble in C and C++ where stack addresses can temporarily become invalid.\nWith the in-place growable gstacks, objects on the stack are never moved\nand their addresses stay valid (in their lexical scope).\n\nAgain, we can just switch stacks to resume at the original\nyield location: \n```ioke\n(gstack 1)              (gstack 2)              (gstack 3)\n\n|-------------|\n| @prompt A   |\n|-------------|\n|             |\n| ...         |         \n| resume_t* r ~~~~~+--\u003e |-------------|\n|             |    |    | @prompt B   | \n| ...         |    |    |-------------|\n| resume \u003c---------+    |             |   \n|             |         | ...         |         \n.             .         | prompt \u003c------------\u003e |------------|\n.             .         .             .         | @prompt C  |\n                        .             .         |------------|\n                        .             .         | 1+         |\n                                                | 42         |\u003c\u003c\u003c\n                                                .            .\n                                                .            .\n```\n\nSuppose, gstack 3 now returns normally with a result 43:\n\n```ioke\n(gstack 1)              (gstack 2)              (gstack 3)\n\n|-------------|\n| @prompt A   |\n|-------------|\n|             |\n| ...         |         \n| resume_t* r ~~~~~+--\u003e |-------------|\n|             |    |    | @prompt B   | \n| ...         |    |    |-------------|\n| resume \u003c---------+    |             |   \n|             |         | ...         |         \n.             .         | prompt \u003c------------\u003e |------------|\n.             .         .             .         | @prompt C  |\n                        .             .         |------------|\n                        .             .         | 43         |\u003c\u003c\u003c\n                                                .            .\n                                                .            .\n```\n\nThen the gstacks can unwind like a regular stack (this is\nalso how exceptions are propagated):  (rule `(RETURN)`)\n\n```ioke\n(gstack 1)              (gstack 2)              (gstack 3)\n\n|-------------|\n| @prompt A   |\n|-------------|\n|             |\n| ...         |         \n| resume_t* r ~~~~~+--\u003e |-------------|\n|             |    |    | @prompt B   | \n| ...         |    |    |-------------|\n| resume \u003c---------+    |             |   \n|             |         | ...         |         \n.             .         | 43          |\u003c\u003c\u003c      (cached to reuse)\n.             .         .             .         |------------|\n                        .             .         |            |\n                        .             .         |            |\n                                                .            .\n                                                .            .\n```\n\nSee [`mprompt.c`](src/mprompt/mprompt.c) for the implementation of this.\n\n## An Example\n\nHere is an example of running `N` \"async\" workers over `M` requests\nusing resumptions as first-class values stored in the `workers` array:\n\n```C\n#include \u003cstdlib.h\u003e\n#include \u003cstdio.h\u003e\n#include \u003cstdint.h\u003e\n#include \u003cmprompt.h\u003e\n\n#define N 1000       // max active async workers\n#define M 1000000    // total number of requests\n\nstatic void* await_result(mp_resume_t* r, void* arg) {\n  (void)(arg);\n  return r;  // instead of resuming ourselves, we return the resumption as a \"suspended async computation\" (A)\n}\n\nstatic void* async_worker(mp_prompt_t* parent, void* arg) {\n  (void)(arg);\n  // start a fresh worker\n  // ... do some work\n  intptr_t partial_result = 0;\n  // and await some request; we do this by yielding up to our prompt and running `await_result` (in the parent context!)\n  mp_yield( parent, \u0026await_result, NULL );\n  // when we are resumed at some point, we do some more work \n  // ... do more work\n  partial_result++;\n  // and return with the result (B)\n  return (void*)(partial_result);\n}\n\nstatic void async_workers(void) {\n  mp_resume_t** workers = (mp_resume_t**)calloc(N,sizeof(mp_resume_t*));  // allocate array of N resumptions\n  intptr_t count = 0;\n  for( int i = 0; i \u003c M+N; i++) {  // perform M connections\n    int j = i % N;               // pick an active worker\n    // if the worker is actively waiting (suspended), resume it\n    if (workers[j] != NULL) {  \n      count += (intptr_t)mp_resume(workers[j], NULL);  // (B)\n      workers[j] = NULL;\n    }\n    // and start a fresh worker and wait for its first yield (suspension). \n    // the worker returns its own resumption as a result.\n    if (i \u003c M) {\n      workers[j] = (mp_resume_t*)mp_prompt( \u0026async_worker, NULL );  // (A)\n    }\n  }\n  printf(\"ran %zd workers\\n\", count);\n}\n\nint main() {\n  async_workers();\n  return 0;\n}\n```\n\n\n\n# The libmpeff Interface\n\nA small library on top of `libmprompt` that implements\nalgebraic effect handlers. Effect handlers give more structure\nthan basic multi-prompts and are a better abstraction for\nprogramming. In particular, \n\n- You do not need the particular prompt marker, but always\n  yield to the innermost handler for a particular effect. This \n  is much more convenient and is essential for example model\n  dynamically bound state (much like implicit parameters).\n\n- All potential operations are bound statically at the handler\n  and you always yield to a particular operation providing arguments\n  (where the handler definition is basically a v-table with a slot for every operation).\n  This makes it easier to reason about than using a multi-prompt\n  yield which can yield with any arbitrary function.\n\n- As effect handlers are linked on the stack, this abstraction\n  can be used across libraries/languages and is thus more\n  composable than using multi-prompts directly.\n\nSee [`effects.c`](test/effects.c) for many examples of common \neffect patterns.\n\n```C\n// handle an effect \nvoid* mpe_handle(const mpe_handlerdef_t* hdef, void* local, mpe_actionfun_t* body, void* arg);\n\n// perform an operation\nvoid* mpe_perform(mpe_optag_t optag, void* arg);\n\n// resume from an operation clause (in a mp_handler_def_t)\nvoid* mpe_resume(mpe_resume_t* resume, void* local, void* arg);\nvoid* mpe_resume_final(mpe_resume_t* resume, void* local, void* arg);\nvoid* mpe_resume_tail(mpe_resume_t* resume, void* local, void* arg); \nvoid  mpe_resume_release(mpe_resume_t* resume);\n```\n\nHandler definitions:\n\n```C\n// An action executing under a handler\ntypedef void* (mpe_actionfun_t)(void* arg);\n\n// A function than handles an operation receives a resumption\ntypedef void* (mpe_opfun_t)(mpe_resume_t* r, void* local, void* arg);\n\n// Operation kinds can make resuming more efficient\ntypedef enum mpe_opkind_e {\n  ...\n  MPE_OP_TAIL,          \n  MPE_OP_GENERAL      \n} mpe_opkind_t;\n\n// Operation definition\ntypedef struct mpe_operation_s {\n  mpe_opkind_t opkind;  \n  mpe_optag_t  optag;   \n  mpe_opfun_t* opfun; \n} mpe_operation_t; \n\n// Handler definition\ntypedef struct mpe_handlerdef_s {\n  mpe_effect_t      effect;         \n  mpe_resultfun_t*  resultfun;     \n  ...\n  mpe_operation_t   operations[8];\n} mpe_handlerdef_t;\n```\n\n[Koka]: https://koka-lang.github.io\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoka-lang%2Flibmprompt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkoka-lang%2Flibmprompt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoka-lang%2Flibmprompt/lists"}