{"id":18014641,"url":"https://github.com/dhil/ocaml-multicont","last_synced_at":"2026-03-01T13:01:29.101Z","repository":{"id":76216509,"uuid":"444075774","full_name":"dhil/ocaml-multicont","owner":"dhil","description":"multi-shot continuations in OCaml","archived":false,"fork":false,"pushed_at":"2025-02-12T10:28:37.000Z","size":200,"stargazers_count":48,"open_issues_count":1,"forks_count":2,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-04-08T00:24:16.478Z","etag":null,"topics":["continuations","deep","effect-handlers","multi-shot","ocaml","shallow"],"latest_commit_sha":null,"homepage":"","language":"OCaml","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/dhil.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.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":"2022-01-03T13:43:46.000Z","updated_at":"2025-02-12T10:28:40.000Z","dependencies_parsed_at":"2024-01-04T14:28:01.284Z","dependency_job_id":"70296836-b979-400e-9335-c12e3195b30d","html_url":"https://github.com/dhil/ocaml-multicont","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/dhil/ocaml-multicont","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhil%2Focaml-multicont","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhil%2Focaml-multicont/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhil%2Focaml-multicont/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhil%2Focaml-multicont/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dhil","download_url":"https://codeload.github.com/dhil/ocaml-multicont/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhil%2Focaml-multicont/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29969700,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T12:56:10.327Z","status":"ssl_error","status_checked_at":"2026-03-01T12:55:24.744Z","response_time":124,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["continuations","deep","effect-handlers","multi-shot","ocaml","shallow"],"created_at":"2024-10-30T04:10:30.220Z","updated_at":"2026-03-01T13:01:29.083Z","avatar_url":"https://github.com/dhil.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Multicont: Continuations with multi-shot semantics in OCaml\n\n[![Multicont build, install, and tests](https://github.com/dhil/ocaml-multicont/actions/workflows/default.yml/badge.svg)](https://github.com/dhil/ocaml-multicont/actions/workflows/default.yml)\n\nThis library provides a thin abstraction on top of OCaml's regular\nlinear continuations that enables programming with multi-shot\ncontinuations, i.e. continuations that can be applied more than once.\n\nSee the\n[`examples/`](https://github.com/dhil/ocaml-multicont/tree/master/examples)\ndirectory for concrete uses of this library (or multi-shot\ncontinuations) in practice.\n\n## Installing the library\n\nThe library can be installed via [OPAM](https://opam.ocaml.org/). The\nlatest release can be installed directly from the default OPAM\nrepository, e.g.\n\n```\n$ opam install multicont\n```\n\nAlternatively, the latest development version can be installed by\npinning this repository, e.g.\n\n```\n$ opam pin multicont git@github.com:dhil/ocaml-multicont.git\n```\n\n### Building and installing from source\n\nIt is straightforward to build and install this library from source as\nits only dependencies are an [OCaml\n5.0+](https://github.com/ocaml/ocaml) compiler,\n[dune](https://github.com/ocaml/dune), and\n[dune-configurator](https://github.com/ocaml/dune). To build the whole\nlibrary simply invoke the `all` rule, i.e.\n\n```shell\n$ make all\n```\n\nTo install the library built from source simply invoke the `install`\nrule:\n\n```shell\n$ make install\n```\n\nSimilarly to uninstall the library again invoke the `uninstall` rule:\n\n```shell\n$ make uninstall\n```\n\n## Configurable options\n\nThe primary reason to build from source is to toggle configurable\noptions of this library, which are not readily available via OPAM\ninstall. Currently, there is only one configurable option:\n\n* `UNIQUE_FIBERS` (default: disabled): Since commit\n[ocaml/ocaml#e12b508](https://github.com/ocaml/ocaml/commit/e12b508876065723ed5fc35c0945030c9b7cd100)\nstock OCaml fibers have been equipped with unique identifiers. Enable\nthis option to preserve unique identities amongst fibers as without\nthis option a fiber clone is an exact copy of the original fiber,\nincluding its identity. By enabling this option, a cloned fiber will\nbe assigned a new unique identity.\n\nConfigurable options are toggled directly on the command line as a\nprefix to the `make` command. For instance, the following enables\nunique fiber identities:\n\n```shell\n$ UNIQUE_FIBERS=1 make all\n```\n\nSetting an option to `1` enables it, whereas any other possible\nassignment disables it.\n\n## The multi-shot continuations interface\n\nThis library is designed to be used in tandem with the `Effect`\nmodule, which provides the API for regular linear continuations. The\nstructure of this library mirrors that of `Effect` as it provides\nsubmodules for the `Deep` and `Shallow` variations of\ncontinuations. This library intentionally uses a slightly different\nterminology than `Effect` in order to allow both libraries to be\nopened in the same scope. For example, this library uses the\nterminology `resumption` in place of `continuation`. A resumption\nessentially amounts to a GC managed variation of a regular OCaml\ncontinuation, which in addition can be continued multiple times.  The\nsignature file\n[multicont.mli](https://github.com/dhil/ocaml-multicont/blob/master/multicont.mli)\ncontains the interface for this library, which I have inlined below:\n\n```ocaml\nmodule Deep: sig\n  type ('a, 'b) resumption\n  (** a [resumption] is a managed variation of\n     [Effect.Deep.continuation] that can be used multiple times. *)\n\n  val promote : ('a, 'b) Effect.Deep.continuation -\u003e ('a, 'b) resumption\n  (** [promote k] converts a regular linear deep continuation to a\n      multi-shot deep resumption. This function fully consumes the\n      supplied continuation [k]. *)\n\n  val resume : ('a, 'b) resumption -\u003e 'a -\u003e 'b\n  (** [resume r v] reinstates the context captured by the multi-shot\n      deep resumption [r] with value [v]. *)\n\n  val abort : ('a, 'b) resumption -\u003e exn -\u003e 'b\n  (** [abort r e] injects the exception [e] into the context captured\n      by the multi-shot deep resumption [r]. *)\n\n  val abort_with_backtrace : ('a, 'b) resumption -\u003e exn -\u003e\n                             Printexc.raw_backtrace -\u003e 'b\n  (** [abort_with_backtrace k e bt] aborts the deep multi-shot\n      resumption [r] by raising the exception [e] in [k] using [bt] as\n      the origin for the exception. *)\n\n  (* Primitives *)\n  val clone_continuation : ('a, 'b) Effect.Deep.continuation -\u003e ('a, 'b) Effect.Deep.continuation\n  (** [clone_continuation k] clones the linear deep continuation [k]. The\n      supplied continuation is *not* consumed. *)\n\n  val drop_continuation : ('a, 'b) Effect.Deep.continuation -\u003e unit\n  (** [drop_continuation k] deallocates the memory occupied by the\n      continuation [k]. Note, however, that this function does not clean\n      up acquired resources captured by the continuation. In order to\n      delete the continuation and free up the resources the programmer\n      should instead use `discontinue` from the [Effect.Deep] module. *)\nend\n\nmodule Shallow: sig\n  type ('a, 'b) resumption\n  (** a [resumption] is a managed variation of\n     [Effect.Shallow.continuation] that can be used multiple times. *)\n\n  val promote : ('a, 'b) Effect.Shallow.continuation -\u003e ('a, 'b) resumption\n (** [promote k] converts a regular linear shallow continuation to a\n     multi-shot shallow resumption. This function fully consumes the\n     supplied continuation [k]. *)\n\n  val resume_with : ('c, 'a) resumption -\u003e 'c -\u003e ('a, 'b) handler -\u003e 'b\n  (** [resume r v h] reinstates the context captured by the multi-shot\n      shallow resumption [r] with value [v] under the handler [h]. *)\n\n  val abort_with  : ('c, 'a) resumption -\u003e exn -\u003e ('a, 'b) handler -\u003e 'b\n  (** [abort r e h] injects the exception [e] into the context captured\n      by the multi-shot shallow resumption [r] under the handler [h]. *)\n\n  val abort_with_backtrace : ('c, 'a) resumption -\u003e exn -\u003e\n                             Printexc.raw_backtrace -\u003e ('a, 'b) handler -\u003e 'b\n  (** [abort_with_backtrace k e bt] aborts the shallow multi-shot\n      resumption [r] by raising the exception [e] in [k] using [bt] as\n      the origin for the exception. *)\n\n  (* Primitives *)\n  val clone_continuation : ('a, 'b) Effect.Shallow.continuation -\u003e ('a, 'b) Effect.Shallow.continuation\n  (** [clone_continuation k] clones the linear shallow continuation [k]. The\n      supplied continuation is *not* consumed. *)\n\n  val drop_continuation : ('a, 'b) Effect.Shallow.continuation -\u003e unit\n  (** [drop_continuation k] deallocates the memory occupied by the\n      continuation [k]. Note, however, that this function does not clean\n      up acquired resources captured by the continuation. In order to\n      delete the continuation and free up the resources the programmer\n      should instead use [discontinue_with] from the [Effect.Shallow] module. *)\nend\n```\n\nIt is worth stressing that both `resume`/`resume_with` and\n`abort`/`abort_with` exhibit multi-shot semantics, meaning in the\nlatter case that it is possible to abort a given `resumption` multiple\ntimes.\n\n## Cautionary tales in programming with multi-shot continuations in OCaml\n\nOne must exercise caution when programming with multi-shot\ncontinuations in OCaml, as the programming model for continuations was\ndesigned with single-shot continuations in mind. Consequently, there\nare a couple of hazards that one should be aware of. Broadly, speaking\nwe can classify these hazards into two categories: compiler\noptimisations and effect ordering.\n\n### Compiler optimisation: Heap to stack conversion\n\nThe OCaml compiler and runtime make some assumptions that are false in\nthe presence of multi-shot continuations. This phenomenon is perhaps\nbest illustrated by an example. Concretely, we can consider some\noptimisations performed by the compiler which are undesirable (or\noutright wrong) when programming with multi-shot continuations. An\ninstance of a wrong compiler optimisation is *heap to stack*\nconversion, e.g.\n\n```ocaml\n(* An illustration of how the heap to stack optimisation is broken.\n * This example is adapted from de Vilhena and Pottier (2021) to OCaml 5.3.0.\n * file: heap2stack.ml\n * compile: ocamlopt -I $(opam var lib)/multicont multicont.cmxa heap2stack.ml\n * run: ./a.out *)\n\n(* We first declare an operation `Twice' which we use to implement\n   multiple returns. *)\ntype _ Effect.t += Twice : unit Effect.t\n\n(* In the code below, we interpret `Twice` by cloning its continuation\n   and invoking it twice. In the match expression, the compiler will\n   perform an escape analysis on the reference `i' and deduce that it\n   does not escape the local scope, because it is unaware of the\n   semantics of `perform Twice', hence the optimiser will transform\n   `i' into an immediate on the stack to save a heap allocation. As a\n   consequence, the assertion `(!i = 1)' will succeed twice, whereas\n   it should fail after the second return of `perform Twice'. *)\nlet heap2stack () =\n  match\n    let i = ref 0 in\n    Effect.perform Twice;\n    i := !i + 1;\n    Printf.printf \"i = %d\\n%!\" !i;\n    assert (!i = 1)\n  with\n  | x -\u003e x\n  | effect Twice, k -\u003e\n     Effect.Deep.continue (Multicont.Deep.clone_continuation k) ();\n     Effect.Deep.continue k ()\n\n(* The following does not trigger an assertion failure. *)\nlet _ = heap2stack ()\n\n(* To fix this issue, we can wrap reference allocations in an instance\n   of `Sys.opaque_identity'. However, this is not really a viable fix\n   in general, as we may not have access to the client code that\n   allocates the reference! *)\nlet heap2stack' () =\n  match\n    let i = Sys.opaque_identity (ref 0) in\n    Effect.perform Twice;\n    i := !i + 1;\n    Printf.printf \"i = %d\\n%!\" !i;\n    assert (!i = 1)\n  with\n  | x -\u003e x\n  | effect Twice, k -\u003e\n     Effect.Deep.continue (Multicont.Deep.clone_continuation k) ();\n     Effect.Deep.continue k ()\n\n(* The following triggers an assertion failure. *)\nlet _ = heap2stack' ()\n```\n\nThe wrong behaviour of `heap2stack` is only observed when compiling\nwith `ocamlc` or `ocamlopt`. As of writing, the read-eval-print loop\ninterpreter does not perform the heap to stack conversion, therefore\nrunning it through `ocaml` will cause `heap2stack` to trigger the\nassertion failure as desired.\n\n### Effect ordering: Array initialisation\n\nWe can use multi-shot continuations to inadvertently observe\nimplementation details, which would otherwise be unobservable (inside\nthe language). Lets illustrate this phenomenon with a concrete\nexample.\n\n```ocaml\n(* An illustration of how effect ordering is observable with\n * multi-shot continuations (OCaml 5.3.0).\n * file: efford.ml\n * compile: ocamlopt -I $(opam var lib)/multicont multicont.cmxa efford.ml\n * run: ./a.out  *)\n\n(* We first require a little bit of setup. The following declares an\n   operation `Twice' which we use to implement multiple returns. *)\ntype _ Effect.t += Twice : bool Effect.t\n\n(* The handler `all' interprets `Twice' by enumerating the possible\n   outcomes of its continuation. *)\nlet all : 'a. (unit -\u003e 'a) -\u003e 'a list\n  = fun f -\u003e\n  match f () with\n  | x -\u003e [x]\n  | effect Twice, k -\u003e\n     let xs = Effect.Deep.continue (Multicont.Deep.clone_continuation k) true in\n     let ys = Effect.Deep.continue k false in\n     xs @ ys\n\n(* This function uses the `Twice` operation to initialise a bit vector\n   of length `n`. *)\nlet init_vec : int -\u003e bool array\n  = fun n -\u003e\n  Array.init n (fun _ -\u003e Effect.perform Twice)\n\n(* The array backing the bit vector is imperative, thus one might\n   expect the interpretation of `init_vec 1` with `htwice` to evaluate to\n   `[[|false|];[|false|]]`, where the two arrays have the same\n   identity. Lets see what it evaluates to... *)\nlet _ =\n  match all (fun () -\u003e init_vec 1) with\n  | [[|true|]; [|false|]] -\u003e ()\n  | _ -\u003e assert false\n(* We get two distinct arrays. Lets see what happens if we initialise\n   a vector of length 2: *)\nlet _ =\n  match all (fun () -\u003e init_vec 2) with\n  | [[|true; false|]; [|true; false|]; [|false; false|]; [|false; false|]] -\u003e ()\n  | _ -\u003e assert false\n(* We have four arrays, but only two of them are distinct (both\n   structurally and referentially). What about vectors of length 3? *)\nlet _ =\n  match all (fun () -\u003e init_vec 3) with\n  | [[|true; false; false|] ; [|true; false; false|] ; [|true; false; false|] ; [|true; false; false|];\n     [|false; false; false|]; [|false; false; false|]; [|false; false; false|]; [|false; false; false|]] -\u003e ()\n  | _ -\u003e assert false\n(* We have eight arrays, but again only two of them are distinct. This\n   pattern continues as we increase `n`. So what's going on? It turns\n   out that we are observing an implementation detail of\n   `Array.init`. Its definition is:\n\n     let init l f =\n       if l = 0 then [||] else\n       if l \u003c 0 then invalid_arg \"Array.init\" else\n       let res = create l (f 0) in (* !! *)\n       for i = 1 to pred l do\n         unsafe_set res i (f i)\n       done;\n       res\n\n  The line with the code responsible for the behaviour is highlighted\n  by the (* !! *) comment. Here we evaluate `f 0`, i.e. `perform\n  Twice`, _before_ we allocate the array, meaning the second\n  invocation of the continuation of the first `Twice` causes another\n  array to be allocated, explaining why we always have two distinct\n  arrays and why the first cell is not set to `false` in the first\n  `n/2` arrays of the list.\n\n  Essentially, we are witnessing the ordering between the user-defined\n  operation `Twice` and the native operation for array creation. If we\n  were to swap them, then we get the behaviour we may have expected\n  initially.  *)\n\nlet init' : int -\u003e (int -\u003e bool) -\u003e bool array\n  = fun l f -\u003e\n  if l = 0 then [||] else\n  if l \u003c 0 then invalid_arg \"Array.init\" else\n  let res = Array.make l true in\n  Array.set res 0 (f 0);\n  for i = 1 to pred l do\n    Array.unsafe_set res i (f i)\n  done;\n  res\n\n(* Similar to `init_vec`, except we initialise the bit vector with\n   our modified `init'`. *)\nlet init_vec' : int -\u003e bool array\n  = fun n -\u003e\n  init' n (fun _ -\u003e Effect.perform Twice)\n\n(* Lets rerun the examples from before. *)\nlet _ =\n  match all (fun () -\u003e init_vec' 1) with\n  | [[|false|]; [|false|]] -\u003e ()\n  | _ -\u003e assert false\n(* Here the two arrays are reference equal (i.e. they have the same identity). *)\nlet _ =\n  match all (fun () -\u003e init_vec' 2) with\n  | [[|false; false|]; [|false; false|]; [|false; false|]; [|false; false|]] -\u003e ()\n  | _ -\u003e assert false\nlet _ =\n  match all (fun () -\u003e init_vec' 3) with\n  | [[|false; false; false|]; [|false; false; false|]; [|false; false; false|]; [|false; false; false|];\n     [|false; false; false|]; [|false; false; false|]; [|false; false; false|]; [|false; false; false|]] -\u003e ()\n  | _ -\u003e assert false\n(* Evidently, the contents of the first cell are overridden by the\n   second invocation of the initial continuation of `Twice`. *)\n```\n\nThese behaviours are instances of the behaviour of composing\nnondeterminism and state to yield either backtrackable or\nnon-backtrackable state. Either behaviour can be desirable. The word\nof caution here is that certain implementation details of higher-order\nfunctions may be observed to a greater extent than is possible with\nsingle-shot continuations or exceptions.\n\n## Notes on the implementation\n\nUnder the hood the library uses the regular linear OCaml continuation and\na variation of `clone_continuation` that used to reside in the `Obj`\nmodule of earlier versions of Multicore OCaml. Internally, the\n`resumption` types are aliases of the respective `continuation` types\nfrom the `Effect` module. The ability to resume a continuation more\nthan once is achieved by cloning the original continuation on\ndemand. The key functions `resume`, `resume_with`, `abort`, and\n`abort_with` all clone the provided continuation argument and invoke\nthe resulting clone rather than the original continuation. The library\nguarantees that the original continuation remains cloneable as the\ncall `promote k` deattaches the stack embedded in the continuation\nobject `k`, meaning that the programmer cannot inadvertently destroy\nthe stack via a call to `continue`.\n\n## Acknowledgements\n\nThis work was supported by the UKRI Future Leaders Fellowship\n\"Effect Handler Oriented Programming\" (reference number MR/T043830/1).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhil%2Focaml-multicont","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdhil%2Focaml-multicont","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhil%2Focaml-multicont/lists"}