{"id":13726148,"url":"https://github.com/dannywillems/RML","last_synced_at":"2025-05-07T21:31:30.510Z","repository":{"id":75871908,"uuid":"85734703","full_name":"dannywillems/RML","owner":"dannywillems","description":"ML modules and functors as first-class citizens by unifying ML modules and ML records with path dependent types and subtyping.","archived":false,"fork":false,"pushed_at":"2022-06-03T10:13:28.000Z","size":223,"stargazers_count":36,"open_issues_count":26,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-10T20:25:23.669Z","etag":null,"topics":["dependent-types","module","ocaml","records","subtype"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dannywillems.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-03-21T17:47:05.000Z","updated_at":"2024-11-04T23:58:03.000Z","dependencies_parsed_at":"2023-02-25T01:30:16.671Z","dependency_job_id":null,"html_url":"https://github.com/dannywillems/RML","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/dannywillems%2FRML","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannywillems%2FRML/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannywillems%2FRML/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannywillems%2FRML/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dannywillems","download_url":"https://codeload.github.com/dannywillems/RML/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224654140,"owners_count":17347677,"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":["dependent-types","module","ocaml","records","subtype"],"created_at":"2024-08-03T01:02:54.044Z","updated_at":"2024-11-14T16:33:22.116Z","avatar_url":"https://github.com/dannywillems.png","language":"OCaml","funding_links":[],"categories":["OCaml"],"sub_categories":[],"readme":"RML - ML modules and functors as first-class citizens.\n===\n\nRML mixes ML records and ML modules in a single type called recursive records,\ngiven the particularity that ML modules and ML functors are first-class citizens\ni.e. can be considered like any other terms. RML is the combination of the\nsimple lambda-calculus with subtyping and parametric polymorphism, recursive\ntype, record and dependent type.\n\n**The implementation is entirely written in OCaml and is focused on typing and subtyping algorithms, not evaluation. For this reason, for example, a term `Unimplemented` of type `Bottom` is defined and often used to leave out the implementation, the meaning of the term. Only types are important. RML is simply a proof-of-concept which shows how ML modules can be manipulated as first-class citizens in OCaml.**\n\nIf you want to contribute,\nsee [GitHub issues](https://github.com/dannywillems/RML/issues).\n\nAny suggestion, advice, bug report, test, etc is appreciated.\n\n## Expressions syntax\n\nEvery expression ends with two semicolons.\nHere the list of examples. Lines with `\u003e` are the output of the typing algorithm. New expressions are separated by a new empty line.\n\nExamples:\n```OCaml\n(* File test/MISC/expression.rml.\n   [let x = 42;;] is the expression and the two following lines are the output\n   of the typing algorithm.\n*)\nlet x = 42;;\n\u003e x : \nInt.t\n\nlet f = fun(x : Int.t) -\u003e x;;\n\u003e f : \n∀(x : Int.t) Int.t\n\nlet f = fun(y : Int.t) -\u003e let x = y in x;;\n\u003e f :\n∀(x : Int.t) Int.t\n```\n\n#### Comments\n\nComments can be defined with `(* *)` like in ML.\n\n```OCaml\n(* File test/MISC/comment.rml *)\n(* This is a comment *)\n(* You can also have comments in comments (* like this and (* like this *) *) *)\nlet x = (* It's also allowed in expressions *) 42;;\n\u003e x :\nInt.t\n```\n\n#### Let binding\n\nYou can define top level let binding with the expression `let var = expr`.\n\nExamples:\n```OCaml\n(* File test/MISC/let_binding.rml *)\nlet x = 42;;\n\u003e x : \nInt.t\n\nlet f = fun(x : Int.t) -\u003e x;;\n\u003e f : \n∀(x : Int.t) Int.t\n\nlet f = fun(y : Int.t) -\u003e let x = y in x;;\n\u003e f : \n∀(y : Int.t) Int.t\n```\n\n#### Local bindings\n\nAs in ML, you can define local bindings with the syntax `let ... = ... in ...`.\n\nExamples:\n```OCaml\n(* File test/MISC/local_binding.rml *)\nlet x = 42 in x;;\n\u003e let  x = 42 : Int.t in\nx :\nInt.t\n\nlet f = fun(x : Int.t) -\u003e x in f 42;;\n\u003e let  f = fun (x : Int.t) -\u003e x in\nlet  'ASC_UN-INT_ = 42 : Int.t in\nf 'ASC_UN-INT_ :\nInt.t\n\nlet x = 42 in let y = x in y;;\n\u003e let  x = 42 : Int.t in\nlet  y = x in\ny :\nInt.t\n```\n\nThe second example shows how syntactic sugars are implemented and how RML\ndesugars it. The low level calculus which RML is based on doesn't accept terms\nin applications but only variables. RML comes with syntactic sugars for terms in\napplications. A simple local let binding is done with an unique variable name.\nNo local binding is done for variable due to the avoidance problem when we have\na variable for a module (see below).\n\n**You need to be careful about the avoidance problem when using local bindings.**\n\n```OCaml\n(* File test/MISC/local_binding_error.rml *)\nlet module M = struct\n  type t = Int.t\n  let x : self.t = 42\nend in M.x;;\n\u003e In file test/MISC/local_binding_error.rml 5:13 : \n  Avoidance Problem (rule LET): M/436 appears in M/436.t.\n```\nis a good example of the avoidance problem because `M.x` is of type `M.t` and\nthe entire expression is of type `M.t` but the module `M` doesn't exist outside\nthe let binding, so it's not well-typed.\n\n#### Functions.\n\nParameters in the functions are annotated with the type. The keyword `fun` is used and a\nsimple right arrow `-\u003e` is used to define the body of the function.\nA function can be curryfied and take multiple parameters by using a comma. But, it\nremains a syntactic sugar for functions returning functions like in many\nfunctional programming languages.\n\nRML also focuses on dependent types and dependent functions. It means parameters\ncan use the parameters defined before.\n\n```OCaml\n(* File test/MISC/function.rml *)\nlet f = fun(x : Int.t) -\u003e x;;\n\u003e f : \n∀(x : Int.t) Int.t\n\nlet g = fun(x : Int.t, y : String.t) -\u003e y;;\n\u003e g : \n∀(x : Int.t) ∀(y : String.t) String.t\n\nlet h = fun(x : Int.t, f : Int.t -\u003e Int.t) -\u003e f x;;\n\u003e h : \n∀(x : Int.t) ∀(f : Int.t -\u003e Int.t) Int.t\n\n(* Is equivalent to *)\nlet h = fun(x : Int.t) -\u003e fun(f : Int.t -\u003e Int.t) -\u003e f x;;\n\u003e h : \n∀(x : Int.t) ∀(f : Int.t -\u003e Int.t) Int.t\n\n(* Here an example of a dependent function. the two last parameters depend on\n   the first parameter which is a module.\n*)\nlet h' = fun(m : sig type t val add : self.t -\u003e self.t -\u003e Int.t end, x : m.t, y : m.t) -\u003e m.add x y;;\n\u003e ∀(m : sig(self)\ntype t = ⟂ .. ⊤\n  val add : self.t -\u003e self.t -\u003e Int.t\nend) ∀(x : m.t) ∀(y : m.t) Int.t\n```\n\n#### Modules and functors.\n\nThe most important thing in RML is modules (and in the same time functors) are\nfirst class citizens. It means you can manipulate modules and functors like any\nother values. You have a good example in the last example about functions.\n\nA module is a term containing a list of declarations which can be recursive (i.e. declarations can be mutually dependent) through a variable surrounding by the keywords `struct` and `end`.\nA declaration is a type declaration (using `type`) or a field declaration (`let`).\n\n```OCaml\n(* File test/MISC/modules_functors.rml *)\n(* Define a simple module with a top level let binding. The module name begins\n   with an uppercase letter and can contain number and underscore.\n*)\nlet module Point2D = struct\n  type t = { x : Int.t ; y : Int.t }\n  let add = fun(p1 : self.t, p2 : self.t) -\u003e\n    let x' = Int.plus p1.x p2.x in\n    let y' = Int.plus p1.y p2.y in\n    { x = x' ; y = y' }\nend;;\n\u003e Point2D :\nsig(self)\ntype t = sig('self)\n  val x : Int.t\n    val y : Int.t\n  end .. sig('self)\n  val x : Int.t\n    val y : Int.t\n  end\n  val add : ∀(p : self.t) ∀(p : self.t) sig('self)\n  val x : Int.t\n    val y : Int.t\n  end\nend\n\n(* In the previous example, the internal variable used to refer to another field\n    is `self` which is the default variable name. Another name can be used as in\n    the following example.\n*)\n(* Instead of self, the variable point is used. *)\nlet module Point2D = struct(point)\n  type t = { x : Int.t ; y : Int.t }\n  let add = fun(p1 : point.t, p2 : point.t) -\u003e\n    let x' = Int.plus p1.x p2.x in\n    let y' = Int.plus p1.y p2.y in\n    { x = x' ; y = y' }\nend;;\n\u003e Point2D : \nsig(point)\ntype t = sig('self)\n  val x : Int.t\n    val y : Int.t\n  end .. sig('self)\n  val x : Int.t\n    val y : Int.t\n  end\n  val add : ∀(p : point.t) ∀(p : point.t) sig('self)\n    val x : Int.t\n    val y : Int.t\n  end\nend\n\n(* You can also use lowercase identifier and don't use the keyword module. The\n   let module syntax is there to differentiate modules from other terms which\n   are common in OCaml.\n*)\n\nlet point2D = struct(point)\n  type t = { x : Int.t ; y : Int.t }\n  let add = fun(p1 : point.t, p2 : point.t) -\u003e\n    let x' = Int.plus p1.x p2.x in\n    let y' = Int.plus p1.y p2.y in\n    { x = x' ; y = y' }\nend;;\n\u003e point2D : \nsig(point)\n  type t = sig('self)\n    val x : Int.t\n    val y : Int.t\n  end .. sig('self)\n    val x : Int.t\n    val y : Int.t\n  end\n  val add : ∀(p : point.t) ∀(p : point.t) sig('self)\n  val x : Int.t\n    val y : Int.t\n  end\nend\n\n(* You can also annotated a module with a signature.\n   It's the equivalent to the following OCaml code.\n   module Point2D_with_sig : sig\n     type t\n     val add : t -\u003e t -\u003e t\n   end = struct\n     type t = { x : int ; y : int }\n     let add x y = { x = p1.x + p2.x ; y = p1.y + p2.y }\n   end\n*)\nlet point2D_with_sig = sig\n  type t\n  val add : self.t -\u003e self.t -\u003e self.t\nend : struct\n  type t = { x : Int.t ; y : Int.t }\n  let add = fun(p1 : self.t, p2 : self.t) -\u003e\n    let x' = Int.plus p1.x p2.x in\n    let y' = Int.plus p1.y p2.y in\n    { x = x' ; y = y' }\nend;;\n\u003e point2D_with_sig : \nsig(self)\n  type t = ⟂ .. ⊤\n  val add : self.t -\u003e self.t -\u003e self.t\nend\n```\n\n**NB: When you annotate the module with a signature, the typing algorithm is naive: it only takes the signature and doesn't perform any verification on the content of the module. The reason is the current inference algorithm on modules is very simple and will fail on some examples. For example, the following code is accepted.**\n\nThis will be changed in the first release.\n\n```OCaml\n(* Even if the body doesn't define a field add, it's allowed by the compiler. *)\nlet point2D_with_sig_error = sig\n  type t\n  val add : self.t -\u003e self.t -\u003e self.t\nend : struct\n  type t = { x : Int.t ; y : Int.t }\nend;;\nsig(self)\n  type t = ⟂ .. ⊤\n  val add : self.t -\u003e self.t -\u003e self.t\nend\n```\n\nFunctors use the same syntax than functions. The following example transforms\nour Point2D module defining points using only integers in a polymorphic points\ntype.\nAs in OCaml, we use the convention functors begin with the uppercase identifier `Make`.\n```OCaml\n(* File test/MISC/modules_functors.rml *)\n(* We create a functor. The first argument of MakePoint2D is a module with a\n   type t and a function plus which takes two elements of type t and returns the\n   same type.\n*)\nlet module MakePoint2D = fun(typ : sig type t val plus : self.t -\u003e self.t -\u003e self.t end) -\u003e struct(point)\n  type t = { x : typ.t ; y : typ.t }\n  let add = fun(p1 : point.t, p2 : point.t) -\u003e\n    let x' = typ.plus p1.x p2.x in\n    let y' = typ.plus p1.y p2.y in\n    { x = x' ; y = y' }\nend;;\n\u003e MakePoint2D : \n∀(typ : sig(self)\n  type t = ⟂ .. ⊤\n  val plus : self.t -\u003e self.t -\u003e self.t\nend) sig(point)\n  type t = sig('self)\n    val x : typ.t\n    val y : typ.t\n  end .. sig('self)\n    val x : typ.t\n    val y : typ.t\n  end\n  val add : ∀(p : point.t) ∀(p : point.t) sig('self)\n    val x : typ.t\n    val y : typ.t\n  end\nend\n\n(* And we can apply a module with the signature to the functor. Int is a good\n   example.\n*)\nlet module Point2DInt = MakePoint2D Int;;\n\u003e Point2DInt : \nsig(point)\n  type t = sig('self)\n    val x : Int.t\n    val y : Int.t\n  end .. sig('self)\n    val x : Int.t\n    val y : Int.t\n  end\n  val add : ∀(p : point.t) ∀(p : point.t) sig('self)\n    val x : Int.t\n    val y : Int.t\n  end\nend\n```\n\n#### Field selection.\n\nYou can access to fields and types of a module with the syntax `M.f` where `M` is the identifier of the module and `f` the field.\n```OCaml\n(* File test/MISC/field_selection.rml *)\n(* We build a module for a list of integers. List is a predefined functor\n   representing lists.\n*)\nlet module ListInt = List Int;;\n\u003e ListInt : \nsig(list)\n  type t = sig(self)\n    val head : Unit.t -\u003e Int.t\n    val tail : Unit.t -\u003e list.t\n    val size : Int.t\n    val is_empty : Bool.t\n  end .. sig(self)\n    val head : Unit.t -\u003e Int.t\n    val tail : Unit.t -\u003e list.t\n    val size : Int.t\n    val is_empty : Bool.t\n  end\n  val empty : list.t\n  val cons : Int.t -\u003e list.t -\u003e list.t\nend\n\n(* We can use the field `cons` which add an element at the beginning to a list,\n   here the empty list, given by ListInt.empty.\n)\nlet l_42 = ListInt.cons 42 ListInt.empty;;\n\u003e l_42 :\nListInt.t\n\n(* And get the head of the list *)\nl_42.head ();;\n\u003e let  'F-SEL_l_42_head = l_.head in\nlet  'F-SEL_Unit_unit = Unit.unit in\n'F-SEL_l_42_head 'F-SEL_Unit_unit :\nInt.t\n\n(* Get the size *)\nl_42.size;;\n\u003e l_.size :\nInt.t\n```\n\n#### Records.\n\nA record is a list of couples `(id, terms)` and the syntax is the same than in OCaml.\n\nRecords and modules have the same internal representation\n(`Grammar.TermRecursiveRecord` or `Grammar.TermRecursiveRecordUntyped`).\n\n*Implementation details:*\nRecords are modules which can not contain fields referring to the module itself.\nTo forbid it, the variable which binds internally to the module is `'self` . As\nquotes in the beginning of a variable name are not allowed in the lexer, we are\nsure fields in the records doesn't refer to other fields.\n\n\n```OCaml\n(* File test/MISC/record.rml *)\nlet x = { a = 42 ; x = 52 };;\n\u003e x :\nsig('self)\n  val a : Int.t\n  val x : Int.t\nend\n\nlet f = fun(x : {a : Int.t ; b : Int.t}) -\u003e Int.succ x.a;;\n\u003e f : \n∀(x : sig('self)\n  val a : Int.t\n  val b : Int.t\nend) ∀(typ : sig(self) type t = ⟂ .. ⊤ end) ∀(s : typ.t -\u003e typ.t) ∀(z : typ.t) typ.t\n\nlet g = fun(x : {a : Int.t}) -\u003e x.a in f { a = 42 ; b = 52 };;\n\u003e let  g = fun (x : sig('self) val a : Int.t end) -\u003e x.a in\nlet \n'UN-REC_'self_AGG_F-DECL_a_F-DECL_b\n=\nstruct('self)\n  let a = 42 : Int.t\n  let b = 52 : Int.t\nend in\nf 'UN-REC_'self_AGG_F-DECL_a_F-DECL_b\n∀(typ : sig(self) type t = ⟂ .. ⊤ end) ∀(s : typ.t -\u003e typ.t) ∀(z : typ.t) typ.t\n```\n\n#### Syntactic sugar.\n\nRML is based on DOT (for Dependent Object Types) calculus, initially developed\nas a basis for the Scala language. This version on RML is based on the syntax,\nsemantics and typing/subtyping rules given\nin\n[Wadler Fest 2016](https://www.cs.purdue.edu/homes/rompf/papers/amin-wf16.pdf).\n\nThe syntax is very low level and not very convenient for writing real programs.\nFor example, you can't\n- define function with multiple arguments.\n- only apply one variable (not terms) to functions (which must be a variable).\n \nThis implies RML has a syntax which supports it.\n\nFor example, you can use terms in function applications and use multiple\narugments in applications. Let bindings are created in the generated AST.\n\n## Types\n\n#### Int (stdlib/int.rml)\n\nAny integer can be used as usual. For example, `42`, `1764`, `0`, ...\nA part is defined with the Church encoding and another with `Unimplemented`. The\ntype of integers is `Int.t`. The module for integers is `Int`.\n\n#### Unit (stdlib/unit.rml)\n\nThe unit term is defined in the module `Unit`. The\ntype of `unit` is `Unit.t`. The term `unit` is `Unit.unit`. A syntactic sugar\n`()` is also available.\n\n```OCaml\nlet example_unit = ();;\nlet example_unit_no_sugar = Unit.unit;;\n```\n\n#### Boolean (stdlib/bool.rml)\n\nBooleans are defined with the Church encoding. The\ntwo values `true` and `false` are respectively `Bool.true` and `Bool.false`.\n\n#### Char (stdlib/char.rml), String (stdlib/string.rml), Float (stdlib/float.rml)\n\nFunctions on these types are not implemented. It's just there to be able to use\nit in some functions and do some tests.\nNo syntactic sugar or real syntax is given to create values of these types.\n\n#### Pair (stdlib/pair.rml)\n\nPairs are built like a functor taking two types. No syntactic sugars are\nprovided for this.\n\n```OCaml\n(* Pair implementation *)\nlet module Pair = fun(left_typ : sig type t end,\n                      right_typ : sig type t end) -\u003e struct(pair)\n  type t = sig\n    val fst : Unit.t -\u003e left_typ.t\n    val snd : Unit.t -\u003e right_typ.t\n  end\n  let init : left_typ.t -\u003e right_typ.t -\u003e pair.t =\n    fun(f : left_typ.t, s : right_typ.t) -\u003e struct\n       let fst = fun(u : Unit.t) -\u003e f\n       let snd = fun(u : Unit.t) -\u003e s\n     end\nend;;\n\u003e Pair : \n∀(left_typ : sig(self) type t = ⟂ .. ⊤ end) ∀(right_typ : sig(self)\ntype t = ⟂ .. ⊤\nend) sig(pair)\ntype t = sig(self)\n    val fst : Unit.t -\u003e left_typ.t\n    val snd : Unit.t -\u003e right_typ.t\n  end .. sig(self)\n    val fst : Unit.t -\u003e left_typ.t\n    val snd : Unit.t -\u003e right_typ.t\n  end\n  val init : left_typ.t -\u003e right_typ.t -\u003e pair.t\nend\n```\n\nAnother implementation without functor is defined in `stdlib/pair_with.rml` in the module `PairWith`. Here the definitation and an example.\n\n```OCaml\n(* File stdlib/pair_with.rml *)\n(* Pair implementation without functor. *)\nlet module PairWith = struct(pair_mod)\n  type pair = sig\n    type fst_typ\n    type snd_typ\n    val fst : self.fst_typ\n    val snd : self.snd_typ\n  end\n  let init =\n    fun(\n      fst_typ : sig type t end,\n      snd_typ : sig type t end,\n      fst : fst_typ.t,\n      snd : snd_typ.t) -\u003e\n    struct\n      type fst_typ = fst_typ.t\n      type snd_typ = snd_typ.t\n      let fst = fst\n      let snd = snd\n    end\nend;;\n\n(* File test/typing/pair_with.rml *)\nlet int_42_42 = PairWith.init Int Int 42 42;;\nint_42_42.fst;;\nint_42_42.snd;;\n```\n#### Intersection and sugar `with`.\n\nIntersection can also be defined with the keyword `\u0026`. The intersection can be\nused with any type. For example `{ x : Int.t} \u0026 {y : Int.t}`.\n\nLike OCaml, the keyword `with` followed by a type declaration can also be used.\n\n```\n(* File stdlib/list_with.rml *)\n(* Polymorphic list implementation based on WF 2016. *)\nlet module List = struct(sci)\n  type list = sig\n    type t\n    val is_empty : Bool.t\n    val head : self.t\n    val tail : sci.list with type t \u003c: self.t\n  end\n  let nil : sci.list with type t = Nothing = struct\n    type t = Nothing\n    let is_empty = Bool.true\n    let head = Unimplemented\n    let tail = Unimplemented\n  end\n  let cons = fun(typ : sig type t end, head : typ.t, tail : sci.list with type t \u003c: typ.t) -\u003e\n  struct\n    type t = typ.t\n    let is_empty = Bool.false\n    let head = head\n    let tail = tail\n  end\nend;;\n\n(* The following line doesn't terminate because it's an infinite derivation tree. *)'\nlet l_42 = List.cons Int 42 List.nil;;\n```\n\n#### List (stdlib/list.rml)\n\nLists are polymorphic as in a previous example. The module `List` is a\nfunctor taking a module containing a type `t`.\nNo syntactic sugar is provided for the moment.\n\nSee `test/typing/list.rml` for good examples.\n\nAnother implementation, based\non\n[Wadler Fest 2016](https://www.cs.purdue.edu/homes/rompf/papers/amin-wf16.pdf)\nis also provided in `stdlib/list_with.rml` but the algorithm doesn't\nterminate due to infinite derivation tree.\n\n#### Option (stdlib/option_church.rml)\n\nOptions are defined with the Church encoding and are defined by the functor\n`Option` which needs a module containing a type `t` for the `some` term.\nYou can define a `some` with `YourOption.some value` and a `none` with\n`YourOption.none`.\n\n```OCaml\n(* file test/typing/option.rml *)\nlet module optionint = option int;;\n\u003e sig(opt)\ntype t = ∀(b : sig(self) type t = ⟂ .. ⊤ end) Int.t -\u003e b.t -\u003e b.t -\u003e b.t .. ∀(b : sig(self)\n  type t = ⟂ .. ⊤\n  end) Int.t -\u003e b.t -\u003e b.t -\u003e b.t\n  val some : Int.t -\u003e opt.t\n  val none : opt.t\n  val match : ∀(returned_type : sig(self) type t = ⟂ .. ⊤ end) ∀(option : opt.t) ∀(is_some : Int.t -\u003e returned_type.t) ∀(is_none : returned_type.t) returned_type.t\nend\n\nlet some_42 = optionint.some 42;;\n\u003e some_42 : \nOptionInt.t\n\nlet none_int = optionint.none;;\n\u003e none_int : \nOptionInt.t\n\nlet f = fun(opt : optionint.t) -\u003e\n  optionint.match\n    (* the type of the match is in the given module in the type t *)\n    int\n    (* the option value *)\n    opt\n    (* some case *)\n    (fun(x : int.t) -\u003e int.plus x 42)\n    (* none case *)\n    (42);;\n\u003e f : \n∀(opt : OptionInt.t) Int.t\n\nf some_42;;\n\u003e f some_ :\nInt.t\n\nf none_int;;\n\u003e f none_int :\nInt.t\n\n(* As match is a function, you can define patterm matching with default values\n   for some cases. in ml, it's a warning about non-exhaustive pattern mathing.\n*)\nlet f_with_no_none_case = fun(opt : optionint.t) -\u003e\n  optionint.match\n    (* the type of the match is in the given module in the type t *)\n    int\n    (* the option value *)\n    opt\n    (* some case *)\n    (fun(x : int.t) -\u003e int.plus x 42);;\n    (* none case is not used. *)\n\u003e f_with_no_none_case : \n∀(opt : OptionInt.t) ∀(is_none : Int.t) Int.t\n\nf_with_no_none_case none_int (int.plus 42 42);;\n\u003e let \n'LET_'F-SEL_Int_plus44_F-SEL_Int_plus_LET_'ASC_UN-INT_4245_ASC_UN-INT_42_LET_'ASC_UN-INT_4246_ASC_UN-INT_42_LET_'VAR_'ASC_UN-INT_424547_APP_'F-SEL_Int_plus44_'ASC_UN-INT_4245_APP_'VAR_'ASC_UN-INT_424547_'ASC_UN-INT_\n=\nlet  'F-SEL_Int_plus = Int.plus in\n  let  'ASC_UN-INT_ = 42 : Int.t in\n  let  'ASC_UN-INT_ = 42 : Int.t in\n  let  'VAR_'ASC_UN-INT_ = 'F-SEL_Int_plus 'ASC_UN-INT_ in\n  'VAR_'ASC_UN-INT_ 'ASC_UN-INT_ in\nlet  'VAR_none_int = f_with_no_none_case none_int in\n'VAR_none_int 'LET_'F-SEL_Int_plus44_F-SEL_Int_plus_LET_'ASC_UN-INT_4245_ASC_UN-INT_42_LET_'ASC_UN-INT_4246_ASC_UN-INT_42_LET_'VAR_'ASC_UN-INT_424547_APP_'F-SEL_Int_plus44_'ASC_UN-INT_4245_APP_'VAR_'ASC_UN-INT_424547_'ASC_UN-INT_ :\nInt.t\n```\n\n#### Sum\n\nVery close to `Option`. It also uses the Church encoding. Sum are polymorphic so\nas previous types, the module `Sum` is actually a functor with two types. Only\ntwo values can be defined (`left` and `right`) and a match can be done with\n`match` field. It takes the module containing the type `t` which must be\nreturned by the pattern matching, the sum value, and two functions, one in the\nleft case, one in the right case which parameter type depends on the branches\nand the returned type is the returned typed of the pattern matching.\n\n```\n(* File /test/typing/sum.rml *)\n(* We crate a Sum type for Int and Float. The left value will be Int and rhe\n   right Float.\n   It's like\n   type t = Left of int | Right of float\n*)\nlet module SumIntFloat = Sum Int Float;;\n\u003e sig(sum)\ntype t = ∀(b : sig(self) type t = ⟂ .. ⊤ end) Int.t -\u003e b.t -\u003e Float.t -\u003e b.t -\u003e b.t .. ∀(b : sig(self)\n  type t = ⟂ .. ⊤\n  end) Int.t -\u003e b.t -\u003e Float.t -\u003e b.t -\u003e b.t\n  val left : Int.t -\u003e sum.t\n  val right : Float.t -\u003e sum.t\n  val match : ∀(return_type : sig(self) type t = ⟂ .. ⊤ end) ∀(s : sum.t) ∀(is_left : Int.t -\u003e return_type.t) ∀(is_right : Float.t -\u003e return_type.t) return_type.t\nend\n\n(* We create a value of type Sum.t by using the variant Left. *)\nlet sum_42_int = SumIntFloat.left 42;;\n\u003e sum_42_int : \nSumIntFloat.t\n\n(* And we create a value of type Sum.t by using the variant Right. *)\nlet sum_42_float = SumIntFloat.right (float_of_int 42);;\n\u003e sum_42_float : \nSumIntFloat.t\n\n(* We can do a pattern matching on a value of type SumintFloat.t like this. *)\nlet f = fun(sum : SumIntFloat.t) -\u003e\n  SumIntFloat.match\n    Int\n    sum\n    (* Left case. *)\n    (fun(x : Int.t) -\u003e x)\n    (* Right case. *)\n    (fun(x : Float.t) -\u003e Float.int_of_float x);;\n\u003e f : \n∀(sum : SumIntFloat.t) Int.t\n\nf sum_42_float;;\n\u003e f sum_42_float :\nInt.t\n\nf sum_42_int;;\n\u003e f sum_42_int :\nInt.t\n\n(* We can also change the type of the patterm matching. *)\nlet module IntList = List Int;;\n\u003e IntList : \nsig(list)\n  type t = sig(self)\n    val head : Unit.t -\u003e Int.t\n    val tail : Unit.t -\u003e list.t\n    val size : Int.t\n    val is_empty : Bool.t\n  end .. sig(self)\n    val head : Unit.t -\u003e Int.t\n    val tail : Unit.t -\u003e list.t\n    val size : Int.t\n    val is_empty : Bool.t\n  end\n  val empty : list.t\n  val cons : Int.t -\u003e list.t -\u003e list.t\nend\n\nlet f' = fun(sum : SumIntFloat.t) -\u003e\n  SumIntFloat.match\n    IntList\n    sum\n    (fun(x : Int.t) -\u003e IntList.cons x IntList.empty)\n    (fun(x : Float.t) -\u003e\n       let x = Float.int_of_float x in\n       IntList.cons x IntList.empty)\n    ;;\n\u003e f' : \n∀(sum : SumIntFloat.t) IntList.t\n\nf' sum_42_float;;\n\u003e f' sum_42_float\nIntList.t\n\nf' sum_42_int;;\n\u003e f' sum_42_int\nIntList.t\n```\n\n#### Module type/signature.\n\nThe type of module is also called the signature.\n\nSome syntactic sugar are provided:\n```OCaml\n(* For type declarations in modules. *)\ntype t --\u003e type t = Nothing .. Any\ntype t :\u003e Int.t --\u003e type t = Int.t .. Any\ntype t \u003c: Int.t --\u003e type t = Nothing .. Int.t\ntype t = Int.t --\u003e type t = Int.t .. Int.t\n\n(* When no variable for the internal use in the module signature, `self` is\n   automatically used.\n*)\nsig\n  type t\n  val add : self.t -\u003e Int.t\nend --\u003e sig(self)\n  type t = Nothing .. Any\n  val add self.t -\u003e Int.t\nend\n```\n\n## Compilation.\n\nDependencies:\n```\nopam switch create ./ 4.14.0\neval $(opam env)\nopam install ocamlfind ocamlbuild cppo cppo_ocamlbuild visitors ansiterminal pprint.20211129 menhir -y\ngit submodule update --init --recursive\ncd alphaLib \u0026\u0026 make install\ncd ../\n```\n\nTo compile, use\n```\nmake\n```\n\nIt produces an executable `rml`.\n\n## How to use?\n\nUse `./rml --help` for the complete documentation.\n\nThe produced executable has two parameters: the **file** and the **action**.\n\n**Actions** are algorithms you want to execute/test.\nPossible actions are:\n- use the typing algorithm on terms (`typing`).\n- use the subtyping algorithm on types (`subtype`).\n\nFor example, you can try the subtyping algorithm on the file `test/subtype/stdlib.rml` by using:\n```\n./rml -f test/typing/option.rml -a typing --use-stdlib\n```\n\n## Annotations\n\nLike in OCaml with PPX, you can add some annotations to activate or desactivate some options.\nFor example, you can add `[@show_derivation_tree]` to the end of an expression\n(before `;;`) to ask to show the typing or sub-typing derivation tree (i.e. to\nsee how the algorithms infer the type or decide if the types are sub-type).\nYou can ask multiple annotations by using a comma.\n\nThe list of available annotations are:\n- `show_derivation_tree` to show the derivation tree. (not activated by default)\n- `no_context` will deactivate the print of the environment (i.e. pre-defined\n  values and types) when printing the derivation tree.\n- `check_well_formed` checks at runtime if types (inferred from terms) are well\n  formed. Note that the well-formed algorithm is always called on internal types\n  during sub-typing and typing algorithms.\n\n#### Examples\n\nPrint the derivation tree without the context.\n```OCaml\nlet y = sig\n  type t = Nothing\n  val x : Nothing\n  val f : self.t -\u003e self.t\nend : struct\n  type t = Nothing\n  let x = Unimplemented\n  let f = fun (x : self.t) -\u003e x\nend [@show_derivation_tree, no_context];;\n```\n\nPrint the derivation tree with the context.\n```OCaml\nlet identity = fun (x : Int.t) -\u003e x [@show_derivation_tree] ;;\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdannywillems%2FRML","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdannywillems%2FRML","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdannywillems%2FRML/lists"}