{"id":19115409,"url":"https://github.com/polytypic/rea-ml","last_synced_at":"2025-04-19T00:32:47.252Z","repository":{"id":49351630,"uuid":"517293347","full_name":"polytypic/rea-ml","owner":"polytypic","description":"Effectful OCaml with Objects and Variants","archived":false,"fork":false,"pushed_at":"2023-02-03T07:33:02.000Z","size":422,"stargazers_count":27,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2023-08-18T21:32:58.276Z","etag":null,"topics":["applicatives","asynchronous","checked-exceptions","effects","functors","higher-kinded-types","hobby-project","monads","ocaml","tagless-final"],"latest_commit_sha":null,"homepage":"https://polytypic.github.io/rea-ml/main/api/rea/Rea/index.html","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/polytypic.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-24T10:34:55.000Z","updated_at":"2023-07-24T15:25:49.000Z","dependencies_parsed_at":"2023-02-18T04:25:14.927Z","dependency_job_id":null,"html_url":"https://github.com/polytypic/rea-ml","commit_stats":null,"previous_names":[],"tags_count":3,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polytypic%2Frea-ml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polytypic%2Frea-ml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polytypic%2Frea-ml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polytypic%2Frea-ml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/polytypic","download_url":"https://codeload.github.com/polytypic/rea-ml/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223786795,"owners_count":17202603,"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":["applicatives","asynchronous","checked-exceptions","effects","functors","higher-kinded-types","hobby-project","monads","ocaml","tagless-final"],"created_at":"2024-11-09T04:46:18.770Z","updated_at":"2024-11-09T04:46:19.314Z","avatar_url":"https://github.com/polytypic.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Effectful OCaml with Objects and Variants\n\nThis is a framework for generic composable effectful asynchronous programming\nbasically using only objects and polymorphic variants.\n\nFeatures:\n\n- Ability to write\n  [\"monad generic code\"](https://discuss.ocaml.org/t/abstracting-over-monads-lwt-and-async-and/965).\n- Functors, Applicatives, Monads, ...\n- Extensible environment or reader.\n- Laziness via η-expansion.\n- Inferred checked exception handing.\n- Asynchronous computations.\n- Extensible with user defined effects.\n- Plays well with type inference allowing relatively concise code.\n- Interoperable with existing monadic libraries.\n\nMore specifically, this uses a\n[tagless-final approach](https://okmij.org/ftp/tagless-final/course/optimizations.html#primer)\nwith the signatures specified using object types and a\n[higher-kinded type encoding](https://github.com/ocamllabs/higher). Programs can\nbe written against abstract generic interfaces allowing the same program to be\nexecuted with multiple interpreters. Interpreters are implemented as objects and\n[passed via the reader pattern](https://www.haskellforall.com/2012/05/scrap-your-type-classes.html?showComment=1338305803531#c5117471807774445845).\nPolymorphic variants can be used for errors. The end result is a system that\nprovides a variety of effects (environment, checked exceptions, asynchronicity,\n...) _à la carte_.\n\nThis library requires OCaml version 4.08 or later for\n[binding operators](https://v2.ocaml.org/manual/bindingops.html) and some other\nconvenience features, but it seems a library like this could have already been\nwritten for OCaml 3 (released in 2000). All the elements of this approach have\nbeen known at least since 2014.\n\n## Contents\n\n- [Introduction](#introduction)\n  - [Basics](#basics)\n  - [Extensible environment](#extensible-environment)\n  - [Projections](#projections)\n  - [Interoperability](#interoperability)\n  - [Traversals](#traversals)\n- [Limitations](#limitations)\n\n## Introduction\n\nThe following subsections introduce some aspects of the Rea framework via simple\nexamples. The code snippets are also extracted as a\n[test](src/test/Rea/Introduction.ml) to ensure that they are accurate.\n\n### Basics\n\nTo begin, we just `open` the `Rea` module, which brings a lot of generic\ncombinators to scope:\n\n```ocaml\nopen Rea\n```\n\nLet's look at a rather familiar example, the implementation of a naïve\nexponential time Fibonacci function as an effectful computation. We use such a\ntrivial example in order to focus on some of the basics of the Rea framework. It\nis assumed that the reader has basic familiarity with monads and the like. If\nnot, rest assured, there is\n[no shortage of material introducing monads](https://wiki.haskell.org/Monad_tutorials_timeline)\non the Internet.\n\n```ocaml\nmodule Fib = struct\n```\n\nAnd here is a naïvely written eager Fibonacci function implementation using the\n`pure` and `lift'2` combinators from the framework:\n\n```ocaml\n  let rec eager n =\n    if n \u003c= 1 then\n      pure n\n    else\n      lift'2 ( + ) (eager (n - 2)) (eager (n - 1))\n```\n\nThe `pure` aka `return` combinator should already be familiar to you. The\n`lift'2` combinator (sometimes called `map2`) combinator takes an ordinary\nfunction of two plain value arguments and returns a function that works on\neffectful computations. In this case, the `+` operator is used to combine the\nresults of the two recursive Fibonacci computations as a computation.\n\nThe below definition shows the type inferred for `eager` (after renaming some\ntype variables to match what is used in the Rea framework):\n\n```ocaml\n  let _ =\n    (eager\n      : int -\u003e\n        (\u003c map' : 'e 'a 'b. ('b -\u003e 'a) -\u003e ('R, 'e, 'b, 'D) er -\u003e ('R, 'e, 'a) s\n         ; pair' :\n             'e 'a 'b.\n             ('R, 'e, 'a, 'D) er -\u003e ('R, 'e, 'b, 'D) er -\u003e ('R, 'e, 'a * 'b) s\n         ; pure' : 'e 'a. 'a -\u003e ('R, 'e, 'a) s\n         ; .. \u003e\n         as\n         'D) -\u003e\n        ('R, 'e, int) s)\n```\n\nThe above undoubtedly looks rather complicated. We can simplify it by using type\nabbreviations (class types) that are used in the definition of the framework\nitself. In particular, each of the methods `map'`, `pair'`, and `pure'` seen\nabove are defined by classes of the same name and each of those classes takes\ntwo type parameters `('R, 'D)`:\n\n```ocaml\n  let _ =\n    (eager\n      : int -\u003e\n        (\u003c ('R, 'D) map' ; ('R, 'D) pair' ; ('R, 'D) pure' ; .. \u003e as 'D) -\u003e\n        ('R, 'e, int) s)\n```\n\nThe curious recursive use of the `'D` type parameter makes sure that all of the\nindividual elements of the combined type agree on the type of the whole\ncomposition.\n\nWhy do the class names have an apostrophe at the end? IOW, why `map'` instead of\njust `map`? Well, there are lots of such a classes, multiple are often used in\ntype signatures, and they tend to have fairly common names. The apostrophe is\nthere to allow the classes to be conveniently at the top level of the `Rea`\nmodule with reduced risk of naming collisions with other libraries.\n\nRecall that we used two combinators `pure` and `lift'2` from the framework in\nthe `eager` function. Obviously `pure` corresponds to or requires the `pure'`\nclass. `lift'2` requires both of the `map'` and `pair'` classes. OCaml\nconveniently infers the required combination for us.\n\nWe can simplify the type further. The combination of `map'`, `pair'`, and\n`pure'` is well known. That combination is the signature of the so called\napplicative functor. The Rea library defines the abbreviation `applicative'` (as\na class) for the combination:\n\n```ocaml\n  let _ = (eager : int -\u003e (('R, 'D) #applicative' as 'D) -\u003e ('R, 'e, int) s)\n```\n\nWe are not quite done yet. Ignore the `int -\u003e`. The remaining part of the type\nis of the form `'D -\u003e ('R, 'e, 'a) s`. This is the type of \"effect readers\" of\nthe `('R, 'e, 'a, 'D) er` type which is actually the main abstraction of the Rea\nframework. Combinators in the Rea framework take effect readers as arguments and\nreturn effect readers as results. So, we end up with the following equivalent\ntype:\n\n```ocaml\n  let _ = (eager : int -\u003e ('R, 'e, int, (('R, 'D) #applicative' as 'D)) er)\n```\n\nLet's discuss the general form of the effect reader type `('R, 'e, 'a, 'D) er`.\nThe type has four type arguments:\n\n- `'R` represents a higher-kinded abstract type constructor that is specific to\n  a particular representation of effects.\n\n- `'e` is the type of errors or failures that may be signaled during\n  interpretation of the effect reader.\n\n- `'a` is the type of answers or results of the effect reader in case it does\n  not signal an error.\n\n- `'D` is the type of the dictionary of capabilities or of the environment\n  required by the effect reader. In other words, it is the type of the effect\n  interpreter.\n\nThe `('R, +'e, +'a) s` type we saw earlier is the abstract effect signature\ntype. It represents the application `('e, 'a) 'R` of the higher-kinded `'R` type\nconstructor to the `'e` and `'a` arguments.\n\nNow, let's interpret the effect reader type of our Fibonacci function:\n\n```ml\n  ('R, 'e, int, (('R, 'D) #applicative' as 'D)) er\n```\n\nFirst of all, we can see that it returns an `int` in case it does not fail with\nan error. Speaking of which, the type of errors is `'e`, which, due to\nparametricity, means that it cannot produce errors. In other words, we (and the\nOCaml type system) statically know that it cannot fail. The representation\nidentification type `'R` is also parametric, which means that it does not\nrequire a specific representation. The dictionary type `'D` is also parametric\nand must be a subtype of `applicative'`. In other words, we can run the effect\nreader with any interpreter that implements the `applicative'` effect\ncapabilities.\n\nFor example, we can use the identity monad implementation provided by the Rea\nframework:\n\n```ocaml\n  let () = assert (55 = Identity.of_rea (run Identity.monad (eager 10)))\n```\n\nThe identity monad does not perform any effects per se and the values computed\nduring interpretation are not wrapped. In other words, with the identity monad,\n`('R, 'e, 'a) s` is equivalent to `'a`. This equivalence is witnessed by a pair\nof identity functions `Identity.of_rea` and `Identity.to_rea`. By itself, the\nidentity monad cannot support the error handling effects of the Rea framework\nnor it can support asynchronous computations. It may seem like a useless\ninterpreter, but it actually has many interesting applications.\n\nThe `run` function used above just passes the interpreter to the computation.\nOne could also just write `eager 10 Identity.monad`.\n\nWe can also use the self tail recursive (and\n[Js_of_ocaml](https://ocsigen.org/js_of_ocaml/latest/manual/overview)\n[safe](https://ocsigen.org/js_of_ocaml/latest/manual/tailcall)) interpreter also\nprovided by the Rea framework:\n\n```ocaml\n  let () = assert (`Ok 55 = Tailrec.run Tailrec.sync (eager 10))\n```\n\nThe tail recursive interpreter provides both a synchronous, as seen above, and\nan asynchronous interpreter and also supports error handling. The Rea framework\nalso provides a number of other interpreter implementations, that we could use\nhere, but let's move on.\n\nAt the beginning we mentioned that the `eager` function is written naïvely. The\nproblem with it is that it is eager: as soon as the first argument is passed to\nit, a whole tree of suspended computations is built.\n\nNow, we saw that the result of `eager n` is actually a function \u0026mdash; namely\nan effect reader. Because it is a function, we can use (eta) η-expansion to make\nit lazy. For this purpose the Rea framework provides a simple `eta'0` function\nthat takes a thunk and returns a single parameter function. Using `eta'0` we can\nwrite an η-expanded Fibonacci function as follows:\n\n```ocaml\n  let rec inert n = eta'0 @@ fun () -\u003e\n    if n \u003c= 1 then\n      pure n\n    else\n      lift'2 ( + ) (inert (n - 2)) (inert (n - 1))\n```\n\nThe `inert` and `eager` functions have exactly the same types. The difference is\nthat the η-expanded `inert` function returns in O(1) time with a function\n\n```ml\n  inert n = fun d -\u003e ...\n```\n\nwhile the `eager` function builds a complete computation tree\n\n```ml\n  eager 0 = pure 0\n  eager 1 = pure 1\n  eager 2 = lift'2 (+) (pure 0) (pure 1)\n  eager 3 = lift'2 (+) (pure 1) (lift'2 (+) (pure 0) (pure 1))\n  eager 4 = lift'2 (+) (lift'2 (+) (pure 0) (pure 1))\n                           (lift'2 (+) (pure 1) (lift'2 (+) (pure 0) (pure 1)))\n  ...\n```\n\ntaking exponential time and space.\n\nOf course, when either one of the effect readers is interpreted, it will take\nexponential time due to the naïve exponential Fibonacci algorithm. Again, the\ndifference is that one of the computations is generated lazily on demand while\nthe other is generated eagerly.\n\nJust like with the previous `eager`, we can use multiple interpreters to run\n`inert` computations:\n\n```ocaml\n  let () = assert (55 = Identity.of_rea (run Identity.monad (inert 10)))\n  let () = assert (`Ok 55 = Tailrec.run Tailrec.sync (inert 10))\n```\n\nThis concludes the Fibonacci example.\n\n```ocaml\nend\n```\n\nWe now have a basic understanding of effect readers. They are just functions\nthat take a dictionary of capabilities aka an interpreter as an argument.\n\n### Extensible environment\n\nLet's continue with an example demonstrating the extensible environment of the\nRea approach. To do so, let's implement a very rudimentary interpreter using a\nmodular approach. Although the techniques in this section scale to more\ninteresting language processors, we will keep the example very minimal.\n\nFirst we'll implement a simple arithmetic language:\n\n```ocaml\nmodule Num = struct\n```\n\nFor later use, we'll define a structural type matching the arithmetic language:\n\n```ocaml\n  type 't t =\n    [`Num of int | `Uop of [`Neg] * 't | `Bop of [`Add | `Mul] * 't * 't]\n```\n\nLike in\n\n\u003e [Code reuse through polymorphic variants](https://www.math.nagoya-u.ac.jp/~garrigue/papers/variant-reuse.pdf)\n\u003e by Jacques Garrigue\n\nwe use polymorphic variants with open recursion for the AST representation.\n\nWe will also use open recursion in the `eval` functions:\n\n```ocaml\n  let uop = function\n    | `Neg -\u003e ( ~-)\n\n  let bop = function\n    | `Add -\u003e ( + )\n    | `Mul -\u003e ( * )\n\n  let eval eval =\n    eta'1 @@ function\n    | `Num _ as v -\u003e pure v\n    | `Uop (op, x) -\u003e (\n      eval x \u003e\u003e= function\n      | `Num x -\u003e pure @@ `Num (uop op x)\n      | x -\u003e fail @@ `Error_attempt_to_apply_uop (op, x))\n    | `Bop (op, l, r) -\u003e (\n      eval l \u003c*\u003e eval r \u003e\u003e= function\n      | `Num l, `Num r -\u003e pure @@ `Num (bop op l r)\n      | l, r -\u003e fail @@ `Error_attempt_to_apply_bop (op, l, r))\n```\n\nThe `eta'1` combinator is another way to write η-expanded functions. It takes a\nfunction and returns a two parameter function. The `\u003e\u003e=` aka `bind` aka `let*`\nis for monadic bind and `\u003c*\u003e` aka `pair` aka `let+ ... and+ ...` is for\napplicative pairing of computation.\n\nErrors are reported with the `fail` combinator. We use polymorphic variants for\nerrors.\n\nAfter some cleaning up, the type inferred for `eval` is roughly equivalent to\nthe following definition:\n\n```ocaml\n  let _ =\n    (eval\n      : ('t -\u003e\n        ( 'R,\n          ([\u003e `Error_attempt_to_apply_bop of\n              ([\u003c `Add | `Mul] as 'bop) * ([\u003e `Num of int] as 'v) * 'v\n           | `Error_attempt_to_apply_uop of ([\u003c `Neg] as 'uop) * 'v ]\n           as\n           'e),\n          'v,\n          (\u003c ('R, 'D) sync' ; .. \u003e as 'D) )\n        er) -\u003e\n        [\u003c 't t] -\u003e\n        ('R, 'e, [\u003e `Num of int], 'D) er)\n```\n\nNotice that the above type shows both of the errors that might arise from\n`eval`.\n\nThe `sync'` class is a combination of `monad'` and `errors'` and `errors'` is a\ncombination of`fail'` and `tryin'`. In other words, it provides both the basic\nmonadic capabilities for sequencing and the ability to signal and handle errors.\n\n```ocaml\nend\n```\n\nLet's then move on to implement (lambda) λ-expressions:\n\n```ocaml\nmodule Lam = struct\n```\n\nLike with the arithmetic language we define a type for the language:\n\n```ocaml\n  module Id = String\n\n  type 't t = [`Lam of Id.t * 't | `App of 't * 't | `Var of Id.t]\n```\n\nDeviating from Garrigue's example, we'll use an environment of bindings\n\n```ocaml\n  module Bindings = Map.Make (Id)\n```\n\nthat maps variables to values. Recall that the arithmetic language above knows\nnothing about environments. To pass around the environment we'll use the\nextensible environment of the Rea framework. For that we define a new class\n`['v] bindings` that exposes a `bindings` property:\n\n```ocaml\n  class ['v] bindings :\n    object\n      method bindings : 'v Bindings.t Prop.t\n    end =\n    object\n      val mutable v : 'v Bindings.t = Bindings.empty\n      method bindings = Prop.make (fun () -\u003e v) (fun x -\u003e v \u003c- x)\n    end\n```\n\nThe `Prop.t` type, whose values are introduced by `Prop.make`, is provided by\nthe Rea framework to concisely expose a mutable instance variable as a readable\nand functionally updatable property. To be clear, it is not actually possible to\nobservably mutate the `v` instance variable outside of the `bindings` class.\n\nFor easy access to the `bindings` method we define a trivial extractor:\n\n```ocaml\n  let bindings d = d#bindings\n```\n\nNow we are ready to write the open `eval` function for λ-expressions:\n\n```ocaml\n  let eval eval =\n    eta'1 @@ function\n    | `Lam (i, e) -\u003e\n      let+ bs = get bindings in\n      `Fun (bs, i, e)\n    | `App (f, x) -\u003e (\n      eval f \u003e\u003e= function\n      | `Fun (bs, i, e) -\u003e\n        let* v = eval x in\n        setting bindings (Bindings.add i v bs) (eval e)\n      | f -\u003e fail @@ `Error_attempt_to_apply f)\n    | `Var i -\u003e (\n      get_as bindings (Bindings.find_opt i) \u003e\u003e= function\n      | None -\u003e fail @@ `Error_unbound_var i\n      | Some v -\u003e pure v)\n```\n\nThe `let+` binding operator is the `map` operation of functors. The `get`\ncombinator reads the value of a property extracted from the environment. The\n`setting` combinator runs a computation with the value of a property\nfunctionally updated to the given value. The `get_as` combinator reads a\nproperty and also maps it through the given function.\n\nThe following definition shows a cleaned up type for the `eval` function:\n\n```ocaml\n  let _ =\n    (eval\n      : ('t -\u003e\n        ( 'R,\n          ([\u003e `Error_attempt_to_apply of\n              ([\u003e `Fun of 'v Bindings.t * Id.t * 't] as 'v)\n           | `Error_unbound_var of Id.t ]\n           as\n           'e),\n          'v,\n          (\u003c ('R, 'D) sync' ; 'v bindings ; .. \u003e as 'D) )\n        er) -\u003e\n        [\u003c 't t] -\u003e\n        ('R, 'e, 'v, 'D) er)\n```\n\nNotice the `bindings` as part of the `'D` dictionary of capabilities.\n\n```ocaml\nend\n```\n\nMoving on to compose the full interpreter\n\n```ocaml\nmodule Full = struct\n```\n\nfrom the above parts, we write a recursive `eval` function that dispatches to\none of the above `eval` functions depending on the input:\n\n```ocaml\n  let rec eval = function\n    | #Num.t as e -\u003e Num.eval eval e\n    | #Lam.t as e -\u003e Lam.eval eval e\n```\n\nAgain, here is a cleaned up type for the `eval` function:\n\n```ocaml\n  let _ =\n    (eval\n      : ([\u003c 't Num.t | 't Lam.t] as 't) -\u003e\n        ( 'R,\n          [\u003e `Error_attempt_to_apply of\n             ([\u003e `Fun of 'v Lam.Bindings.t * Lam.Id.t * 't | `Num of int] as 'v)\n          | `Error_attempt_to_apply_bop of [`Add | `Mul] * 'v * 'v\n          | `Error_attempt_to_apply_uop of [`Neg] * 'v\n          | `Error_unbound_var of Lam.Id.t ],\n          'v,\n          (\u003c ('R, 'D) sync' ; 'v Lam.bindings ; .. \u003e as 'D) )\n        er)\n```\n\nNotice how the type combines\n\n- the arithmetic and λ-expressions (as `'t`),\n- the errors (`[\u003e ...]`),\n- the value type (as `'v`), and\n- the capability dictionaries (as `'D`).\n\nTo actually run `eval` we will need an effect interpreter that provides the\n`sync'` capabilities as well as `bindings`. There is an interpreter for the\nstandard `result` type that we can use for the `sync'` capabilities. For the\n`bindings` we can just use `bindings`. Here is how:\n\n```ocaml\n  let () =\n    assert (\n      Error (`Error_unbound_var \"y\")\n      = StdRea.Result.of_rea\n          (run\n             (object\n                inherit [_] StdRea.Result.monad_errors\n                inherit [_] Lam.bindings\n             end)\n             (eval (`App (`Lam (\"x\", `Bop (`Add, `Num 2, `Var \"y\")), `Num 1)))))\n```\n\nAnother interpreter that comes bundled with the Rea framework that provides\n`sync'` is the `Tailrec` interpreter we used previously. So, we could also use\n`Tailrec` as follows:\n\n```ocaml\n  let () =\n    assert (\n      `Ok (`Num 3)\n      = Tailrec.run\n          (object\n             inherit [_] Tailrec.sync\n             inherit [_] Lam.bindings\n          end)\n          (eval (`App (`Lam (\"x\", `Bop (`Add, `Num 2, `Var \"x\")), `Num 1))))\n```\n\nWe could also use the asynchronous version of the `Tailrec` interpreter. To\nensure that neither errors nor results are implicitly ignored, the\n`Tailrec.spawn` function requires that the computation throws `nothing` and\nreturns `()`. We need to wrap the computation with handlers:\n\n```ocaml\n  let () =\n    let result = ref @@ Ok (`Num 0) in\n    Tailrec.spawn\n      (object\n         inherit [_] Tailrec.async\n         inherit [_] Lam.bindings\n      end)\n      (eval (`App (`Lam (\"x\", `Bop (`Add, `Num 2, `Var \"x\")), `Num 1))\n      |\u003e tryin\n           (fun e -\u003e pure (result := Error e))\n           (fun v -\u003e pure (result := Ok v)));\n    assert (!result = Ok (`Num 3))\n```\n\nThe `tryin` combinator allows us to handle errors and continue with results.\nSince we handle all of the errors, the error type for the whole computation\nbecomes parametric and can be unified with `nothing`.\n\nNormally one cannot assume that a computation started with `Tailrec.spawn`\ncompletes immediately. In this case we had nothing asynchronous in the\nimplementation and nothing asynchronous running in the background.\n\n```ocaml\nend\n```\n\nThis concludes the example. We now know about the extensible environment as well\nas about error handling.\n\n### Projections\n\nIn the previous section we wrote a simple modular interpreter that used the\nextensible environment of Rea to pass along the bindings capability. Adding to\nthe extensible environment is easy \u0026mdash; you just declare what you want. In a\nmore complex program different parts of the program might require different\ncapabilities. The top-level of the program then ends up having to know about\nabout all of those capabilities. This is a modularity problem.\n\nImagine using a library using the framework. A new version of the library comes\nalong and your program no longer compiles just because the library now\ninternally needs a different set of capabilities compared to the previous\nversion. That is not good. We need a way to handle effects locally. Ideally we'd\nlike to be able to say that a program requires a specific set of capabilities to\nrun and also requires the freedom to extend the environment with some other\ncapabilities (i.e. that it \"lacks\" or is \"disjoint\" from the additional\ncapabilities). This way the environment could be efficiently extended using some\nform of polymorphic record extension. Unfortunately OCaml's objects do not offer\nsuch a form of polymorphism. What can we do?\n\nOCaml does offer half of what we need: we can declare that we need at least some\nspecific capabilities from the environment. What we can do then is to project\nthose capabilities out of the environment and build our own scoped environment.\n\n```ocaml\nmodule Scoped = struct\n```\n\nLet's see how that is done. Here is an `eval` function that does not require\n`bindings` from the environment:\n\n```ocaml\n  let eval e =\n    Full.eval e\n    |\u003e mapping_env @@ fun o -\u003e\n       object\n         inherit [_, _, _] sync'of o\n         inherit [_] Lam.bindings\n       end\n```\n\nThe `mapping_env` combinator allows us to get the outer environment `o` and\nsubstitute our own. For that we use the `sync'of` class above. It is given an\nobject that must be of some subtype of `sync'`. It then provides the `sync'`\ncapabilities by delegating to the given object. In other words, we project the\n`sync'` capability out of the environment `o`.\n\nThe following definition shows a cleaned up type for the closed `eval`:\n\n```ocaml\n  let _ =\n    (eval\n      : ([\u003c 't Num.t | 't Lam.t] as 't) -\u003e\n        ( 'R,\n          [\u003e `Error_attempt_to_apply of\n             ([\u003e `Fun of 'v Lam.Bindings.t * Lam.Id.t * 't | `Num of int] as 'v)\n          | `Error_attempt_to_apply_bop of [`Add | `Mul] * 'v * 'v\n          | `Error_attempt_to_apply_uop of [`Neg] * 'v\n          | `Error_unbound_var of Lam.Id.t ],\n          'v,\n          (('R, 'D) #sync' as 'D) )\n        er)\n```\n\nThe `bindings` capability no longer appears in the environment type `'D` and we\ncan run it with just the base `Tailrec` interpreter:\n\n```ocaml\n  let () =\n    assert (\n      `Ok (`Num 42)\n      = Tailrec.run Tailrec.sync\n          (eval (`App (`Lam (\"x\", `Bop (`Add, `Num 2, `Var \"x\")), `Num 40))))\n```\n\nBeing able to scope effects in this fashion is important for modularity.\nUnfortunately doing so is not free as each projection adds some delegation\noverhead to the effect invocations. Also, what we dealth with above is the easy\ncase where we modularized a first-order function. Higher-order functions where\nthe caller supplied functions also need to use the environment require wrapping\nthe user supplied functions replacing the environment back to what it was.\n\n```ocaml\nend\n```\n\nThis concludes the example. We now know more about the extensible environment.\n\n### Interoperability\n\nLet's suppose next that we have an existing monadic library that we need to\ninteroperate with. For the sake of argument, let's assume the following\ncontinuation monad implementation:\n\n```ocaml\nmodule Cont : sig\n  type 'a t\n\n  val return : 'a -\u003e 'a t\n  val bind : 'a t -\u003e ('a -\u003e 'b t) -\u003e 'b t\n  val callcc : (('a -\u003e 'b t) -\u003e 'a t) -\u003e 'a t\n  val run : 'a t -\u003e 'a\nend = struct\n  type 'a t = ('a -\u003e unit) -\u003e unit\n\n  let return x k = k x\n  let bind xK xyK k = xK (fun x -\u003e (xyK x) k)\n  let callcc kxK k = kxK (fun x _ -\u003e k x) k\n\n  let run xK =\n    let result = ref None in\n    xK (fun x -\u003e result := Some x);\n    Option.get !result\nend\n```\n\nFirst we notice that we ran into a bit of a snag. Although Rea provides a number\nof related effects, at the time of writing, no `callcc` effect is provided.\nFortunately nothing prevents users from extending the framework. We just write\ndown the `callcc'` class:\n\n```ocaml\nclass virtual ['R, 'D] callcc' =\n  object\n    method virtual callcc'\n        : 'e 'f 'a 'b.\n          (('a -\u003e ('R, 'f, 'b, 'D) er) -\u003e ('R, 'e, 'a, 'D) er) -\u003e ('R, 'e, 'a) s\n  end\n```\n\nAnd the generic effect reader combinator `callcc`:\n\n```ocaml\nlet callcc f (d: (_, _) #callcc') = d#callcc' f\n```\n\nNow, how do we relate `'a Cont.t` with the Rea framework? What we need to do is\nto embed it into the abstract `('R, 'e, 's) s` effect signature type. We create\nan abstract representation type `r` corresponding to the `Cont.t` type\nconstructor and an injection projection pair:\n\n```ocaml\nmodule ContRea = struct\n  type r\n\n  external to_rea : 'a Cont.t -\u003e (r, 'e, 'a) s = \"%identity\"\n  external of_rea : (r, 'e, 'a) s -\u003e 'a Cont.t = \"%identity\"\n```\n\nRea probably should provide a functor for the above, but it currently doesn't.\nThe special\n\n```ml\n  external fn : s -\u003e t = \"%identity\"\n```\n\nconstruct tells OCaml that `fn` is an external function of type `s -\u003e t` that is\nactually the identity function. Because both the above type `r` and the type\n`(_, _, _) s` from Rea are abstract, the above two coercions are safe \u0026mdash; as\nlong as we don't provide other incompatible coercions between the abstract\ntypes.\n\nNow we can write down an interpreter class\n\n```ocaml\n  class ['D] monad_callcc =\n    object (d: 'D)\n      inherit [r, 'D] monad'd\n      method pure' x = to_rea (Cont.return x)\n\n      method bind' x f =\n        to_rea (Cont.bind (of_rea (x d)) (fun x -\u003e of_rea (f x d)))\n\n      inherit [r, 'D] callcc'\n\n      method callcc' f =\n        to_rea (Cont.callcc (fun k -\u003e of_rea (f (fun x _ -\u003e to_rea (k x)) d)))\n    end\n```\n\nbased on `Cont`. As an example, we can use it with the previously defined\nFibonacci computations:\n\n```ocaml\n  let () =\n    assert (55 = Cont.run (of_rea (run (new monad_callcc) (Fib.inert 10))))\n```\n\nAnd `callcc` is also available:\n\n```ocaml\n  let () =\n    assert (\n      101 = Cont.run (of_rea (run (new monad_callcc) (callcc (fun k -\u003e k 101)))))\n```\n\nAll the generic combinators for monads and simpler functors, and the extensible\nenvironment are now available when constructing `Cont` computations via the Rea\nframework.\n\n```ocaml\nend\n```\n\nWe now know that we can write effect interpreters using existing monadic\nlibraries that run their computations embedded into the Rea framework and that\nwe can also introduce new effects into the framework.\n\n### Traversals\n\nWonder what the last example will be about?\n\n\u003e The answer is always traverse. —\n\u003e [Rúnar](https://twitter.com/runarorama/status/1237817671619686400)\n\nIndeed, mapping with an effect reader, `map_er`, or \"traverse\" as it is often\ncalled, tends to be the answer to many problems. So, let's explore the topic a\nbit. We'll build on the earlier modular interpreter example.\n\n```ocaml\nmodule Answer = struct\n```\n\nAlthough we could use a modular approach and build traversal functions modularly\nfor expressions, that's not really the point here. So, let's just work on the\nfull AST. Here is a generic traversal function for the structural AST:\n\n```ocaml\n  let map_er' nE o1E o2E iE eE = eta'1 @@ function\n    | `Num x -\u003e map_er'1 nE        x \u003e\u003e- fun x -\u003e `Num x\n    | `Uop x -\u003e map_er'2 o1E eE    x \u003e\u003e- fun x -\u003e `Uop x\n    | `Bop x -\u003e map_er'3 o2E eE eE x \u003e\u003e- fun x -\u003e `Bop x\n    | `Lam x -\u003e map_er'2 iE eE     x \u003e\u003e- fun x -\u003e `Lam x\n    | `App x -\u003e map_er'2 eE eE     x \u003e\u003e- fun x -\u003e `App x\n    | `Var x -\u003e map_er'1 iE        x \u003e\u003e- fun x -\u003e `Var x\n```\n\nInstead of just traversing over the direct subexpressions of an expression, the\nabove `map_er'` also allows traversing over the numbers, unary and binary\noperators, and identifiers. The constructors of the datatype are traversed in a\nsystematic way using a family of canned `map_er'n` functions for each arity of a\ntuple the constructors are carrying.\n\nWhy call it `map_er` rather than `traverse`? Well, mapping over a datatype with\nan effect constructor is just one of many useful generalizations of ordinary\npure functions:\n\n- `map` -\u003e `map_er`\n- `find` -\u003e `find_er`\n- `fold` -\u003e `fold_er`\n- ...\n\nSystematic naming hopefully makes the API easier to learn.\n\nI find it convenient to implement traversal for a structural type in the above\nmanner as it allows one to easily specialize to the basic traversal over\nsubexpressions\n\n```ocaml\n  let map_er eE = map_er' pure pure pure pure eE\n```\n\nand also use the generic traverse for other purposes.\n\nThe type of the `map_er` over subexpressions is seen in the below definition:\n\n```ocaml\n  type 't t = [ 't Num.t | 't Lam.t ]\n\n  let _ =\n    (map_er\n      : ('s -\u003e ('R, 'e, 't, (('R, 'D) #applicative' as 'D)) er) -\u003e\n        [\u003c 's t] -\u003e\n        ('R, 'e, [\u003e 't t], 'D) er)\n```\n\nWhat can one do with traversals? Well, a lot of things.\n\nFor example, here is a function that recursively traverses an expression to find\nout whether a given variable is free or not in a given expression:\n\n```ocaml\n  let rec is_free i' = function\n    | `Var i -\u003e i = i'\n    | `Lam (i, _) when i = i' -\u003e false\n    | e -\u003e Traverse.to_exists map_er (is_free i') e\n\n  let () = assert (is_free \"y\" (`App (`Lam (\"x\", `Var \"x\"), `Var \"y\")))\n  let () = assert (not (is_free \"x\" (`App (`Lam (\"x\", `Var \"x\"), `Var \"y\"))))\n```\n\nUsing `map_er` we can treat all the \"basic\" cases of the datatype generically\nand focus on the two cases that are interesting with respect to bindings.\n\nBut we kind of got ahead of ourselves. How does `Traverse.to_exists` work?\n\nWell, what `map_er` does is that it maps every element of a datatype to an\neffect and then combines those effects to reconstruct the shape of the datatype.\nIf, for example, we use the previously mentioned `Identity` monad to run the\neffects, then we basically get a `map` function for the datatype.\n\nEffects are not limited to simply returning a value of the answer type. We\nalready previously saw the `fail` effect, which doesn't return an answer.\nAnother example is the `Constant` functor, which, as its name suggests, carries\nalong a constant value of some type and the answer type is a phantom type. The\n`Constant` functor can be augmented to an applicative over a monoid. For\nexample, we can use disjunction. And that is what `Traverse.to_exists` does. It\nmaps the traversed elements to boolean constants as returned by the given\npredicate. Then those booleans are combined using disjunction.\n\nBut this is all quite common knowledge. Why are we discussing this? Well, recall\nthat the Rea framework provides a form of laziness via η-expansion. That same\nlaziness also works with traversals over monoids, among other things. So, when\nwe call `Traverse.to_exists map_er (is_free i') e`, instead of first eagerly\nbuilding the computation for the whole expression tree `e`, as one might expect\nto be the case in a strict language like OCaml, the computation is built on\ndemand and actually stops roughly as soon as the first free variable occurrence\nhas been encountered. So, although the use of objects and whatnot brings quite a\nbit of overhead, at least we actually get the desired asymptotic time complexity\nfor `is_free`.\n\nThis concludes the example and the introduction.\n\n```ocaml\nend\n```\n\nWe now know about most aspects of the Rea framework. Perhaps the best way to\nlearn more is to take a brief look at the\n[reference manual](https://polytypic.github.io/rea-ml/main/api/rea/Rea/index.html)\nand start using the framework. Have fun!\n\n## Limitations\n\nThe main drawbacks of this approach come from the limitations of OCaml's\nobjects:\n\n- OCaml does not aggressively optimize (statically known) method invocations.\n  This means that every effect invocation has some overhead.\n\n- OCaml's object system does not support adding methods to or removing methods\n  from objects (i.e. polymorphic record extension). This means that effects\n  cannot be easily handled locally.\n\nOn the other hand, this approach arguably has a rather straightforward\nimplementation and is convenient to use (modulo the difficulty of handling\neffects locally).\n\nUnfortunately the library\n[reference manual](https://polytypic.github.io/rea-ml/main/api/rea/Rea/index.html)\nis rather unfinished at the moment.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolytypic%2Frea-ml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpolytypic%2Frea-ml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolytypic%2Frea-ml/lists"}