UnifierSig.ml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179(******************************************************************************) (* *) (* Inferno *) (* *) (* François Pottier, Inria Paris *) (* *) (* Copyright Inria. All rights reserved. This file is distributed under the *) (* terms of the MIT License, as described in the file LICENSE. *) (* *) (******************************************************************************) (* This file defines the input and output signatures of the functor [Unifier.Make]. *) (* -------------------------------------------------------------------------- *) (* -------------------------------------------------------------------------- *) (* The structure of types is described to the unifier as follows. *) module type STRUCTURE = sig (* The type ['a structure] should be understood as a type of shallow (that is, depth 1) types whose leaves have type ['a]. *) type 'a structure (* The type ['a structure] should be a functor, i.e., it should support the standard [map] operation. *) val map: ('a -> 'b) -> 'a structure -> 'b structure (* We also require [iter] and [fold]. *) val iter: ('a -> unit) -> 'a structure -> unit val fold: ('a -> 'b -> 'b) -> 'a structure -> 'b -> 'b (* We also require [iter2], which fails if the head constructors differ. *) exception Iter2 val iter2: ('a -> 'b -> unit) -> 'a structure -> 'b structure -> unit end (* -------------------------------------------------------------------------- *) (* -------------------------------------------------------------------------- *) module type UNIFIER = sig (* This is again the type of shallow types. *) type 'a structure (* The unifier maintains a graph whose vertices are known as variables. *) type variable (* Each variable carries three pieces of information, namely: - a unique identifier [id]; - an optional [structure], which defines the constructor and the outgoing edges carried by this variable; - an integer [rank]. The following read & write accessors are offered: *) val id: variable -> int val structure: variable -> variable structure option val set_structure: variable -> variable structure option -> unit val rank: variable -> int val set_rank: variable -> int -> unit (* [adjust_rank v k] is equivalent to [if k < rank v then set_rank v k] and equivalent to [set_rank v (min k (rank v))]. We offer this special-purpose accessor because this may help avoid some errors and gain some speed. *) val adjust_rank: variable -> int -> unit (* -------------------------------------------------------------------------- *) (* A new variable, with specified structure and rank, is obtained as follows. A fresh identifier is automatically picked. *) val fresh: variable structure option -> int -> variable (* -------------------------------------------------------------------------- *) (* The main service offered by the unifier is the function [unify], which merges two variables [v1] and [v2]. The unique identifier of the merged variable is arbitrarily chosen between the identifiers of [v1] and [v2]. The structures carried by [v1] and [v2], if present, are recursively unified: that is, [unify] is recursively invoked for every pair of matching successors. If these structures exhibit incompatible constructors, then [Unify (v1, v2)] is raised, and the state of the unifier is unaffected. Unification may cause cycles in the graph to appear; this is explicitly supported. An occurs check is provided separately; see below. Note that the types [v1] and [v2] carried by the exception [Unify] can be cyclic! The rank of the merged variable is the minimum of the ranks of [v1] and [v2]. *) exception Unify of variable * variable val unify: variable -> variable -> unit (* -------------------------------------------------------------------------- *) (* Because every variable carries a unique identifier, it is easy to define a hash table whose keys are variables. Be careful, though: identifiers are stable only as long as no unification is performed. So, a hash table should not be used across a call to [unify]. *) module VarMap : Hashtbl.S with type key = variable (* -------------------------------------------------------------------------- *) (* [equivalent v1 v2] tells whether [v1] and [v2] belong to the same equivalence class. *) val equivalent: variable -> variable -> bool (* [is_representative] maps exactly one member of each equivalence class to [true]. *) val is_representative: variable -> bool (* -------------------------------------------------------------------------- *) (* [new_occurs_check is_young] initiates a new cycle detection phase. It produces a function [check] which can then be applied to a number of roots. If a cycle is detected, [Cycle] is raised. The occurs check is performed only over the young variables, i.e., those that satisfy the user-supplied predicate [is_young]. *) exception Cycle of variable val new_occurs_check: (variable -> bool) -> (variable -> unit) (* -------------------------------------------------------------------------- *) (* [new_acyclic_decoder leaf fold] initiates a new decoding phase. It produces a function [decode], which can then be applied to a number of roots. Decoding is a bottom-up computation of a value, and requires the graph to be acyclic (see [new_occurs_check] above). The functions [leaf] and [fold] are invoked (only) at a newly discovered variable; [leaf] is invoked if the variable has no structure, while [fold] is invoked if it has some structure. At a variable that has already been visited, the previously computed value is automatically returned. Decoding can be used, for instance, to transform the graph into a tree. Decoding runs in overall linear time and space. However, if the resulting tree is traversed in a naive way, an exponential blowup can take place! *) val new_acyclic_decoder: (* leaf : *) ( variable -> 'a) -> (* fold : *) ('a structure -> 'a) -> ( variable -> 'a) (* -------------------------------------------------------------------------- *) (* [new_cyclic_decoder] is analogous to [new_acyclic_decoder], but supports cyclic graphs. When a cycle is detected at a variable [x], instead of going further down, we stop and produce [leaf x]. Then, when this cycle is exited, also at [x], the function [mu] is used to combine [x] with the value that was obtained at the top of the cycle. The algorithm ensures that every cyclic use of a variable is dominated by a use of [mu]. The code is potentially exponentially inefficient. Use with caution! *) val new_cyclic_decoder: (* leaf : *) ( variable -> 'a) -> (* fold : *) ( 'a structure -> 'a) -> (* mu : *) (variable -> 'a -> 'a) -> ( variable -> 'a) end