Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Unifier.ml
1 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 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
(******************************************************************************) (* *) (* 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. *) (* *) (******************************************************************************) open UnifierSig module Make (S : STRUCTURE) = struct type 'a structure = 'a S.structure (* -------------------------------------------------------------------------- *) (* The data structure maintained by the unifier is as follows. *) (* A unifier variable is a point of the union-find algorithm. *) type variable = descriptor TUnionFind.point (* Every equivalence class carries a descriptor which contains the following information. *) (* Some of the fields below are mutable, because our client sometimes needs to update them. However, this is never done by the unifier itself, hence never done during unification. The unification algorithm is transactional: it writes only [TRef]s, so that all changes can be rolled back if unification fails. *) and descriptor = { (* Every equivalence class carries a globally unique identifier. When a new equivalence class is created, a fresh identifier is chosen, and when two classes are merged, one of the two identifiers is kept. This identifier can be used as a key in a hash table. One should be aware, though, that identifiers are stable only as long as no unions are performed. *) id : int; (* Every equivalence class carries a structure, which is either [None], which means that the variable is just that, a variable; or [Some t], which means that the variable represents (has been equated with) the type [t]. *) mutable structure : variable structure option; (* Every equivalence class carries an integer rank. When two classes are merged, the minimum rank is retained. *) mutable rank : int; } (* -------------------------------------------------------------------------- *) (* Accessors. *) let id v = (TUnionFind.find v).id let structure v = (TUnionFind.find v).structure let set_structure v structure = (TUnionFind.find v).structure <- structure let rank v = (TUnionFind.find v).rank let set_rank v rank = (TUnionFind.find v).rank <- rank let adjust_rank v k = let desc = TUnionFind.find v in if k < desc.rank then desc.rank <- k (* -------------------------------------------------------------------------- *) (* [r++]. *) let postincrement r = let v = !r in r := v + 1; v (* -------------------------------------------------------------------------- *) (* [fresh] creates a fresh variable with specified structure and rank. *) let fresh = let id = ref 0 in fun structure rank -> TUnionFind.fresh { id = postincrement id; structure = structure; rank = rank; } (* -------------------------------------------------------------------------- *) (* The internal function [unify t v1 v2] equates the variables [v1] and [v2] and propagates the consequences of this equation until an inconsistency is found or a solved form is reached. In the former case, [S.Iter2] is raised. The parameter [t] is a transaction. *) let rec unify (t : _ TRef.transaction) (v1 : variable) (v2 : variable) : unit = (* If the two variables already belong to the same equivalence class, there is nothing to do, and [unify_descriptors] is not invoked. Furthermore, if there is something to do, then [unify_descriptors] is invoked only after the two equivalence classes have been merged. This is not just an optimization; it is essential in guaranteeing termination, since we are dealing with potentially cyclic structures. *) TUnionFind.union t (unify_descriptors t) v1 v2 (* -------------------------------------------------------------------------- *) (* [unify_descriptors desc1 desc2] combines the descriptors [desc1] and [desc2], producing a descriptor for the merged equivalence class. *) and unify_descriptors t desc1 desc2 = { (* An arbitrary choice of identifier is ok. *) id = desc1.id; structure = unify_structures t desc1.structure desc2.structure; rank = min desc1.rank desc2.rank; } (* -------------------------------------------------------------------------- *) (* [unify_structures structure1 structure2] combines two structures. If one of them is undefined, we just keep the other. If both are defined, we unify them recursively. *) and unify_structures t structure1 structure2 = Option.multiply (fun t1 t2 -> S.iter2 (unify t) t1 t2; t2 (* arbitrary; [t1] and [t2] are now equal anyway *) ) structure1 structure2 (* -------------------------------------------------------------------------- *) (* The public version of [unify] wraps the unification process in a transaction, so that a failed unification attempt does not alter the state of the unifier. *) exception Unify of variable * variable let unify v1 v2 = try TRef.tentatively (fun t -> unify t v1 v2 ) with S.Iter2 -> raise (Unify (v1, v2)) (* -------------------------------------------------------------------------- *) (* Hash tables whose keys are variables. *) module VarMap = Hashtbl.Make(struct type t = variable let equal = TUnionFind.equivalent let hash v = Hashtbl.hash (id v) end) (* -------------------------------------------------------------------------- *) let equivalent = TUnionFind.equivalent let is_representative = TUnionFind.is_representative (* -------------------------------------------------------------------------- *) (* The occurs check, which detects cycles in the graph, cannot be defined as an instance of the cyclic decoder, for several reasons. For one thing, the cyclic decoder is inefficient, as it does not (cannot) mark the nodes that have been visited. Furthermore, the occurs check explores only the young generation, whereas the decoders explore everything. *) exception Cycle of variable let new_occurs_check (is_young : variable -> bool) = (* This hash table records the variables that are being visited (they are mapped to [false]) or have been visited (they are mapped to [true]). *) let table : bool VarMap.t = VarMap.create 128 in let rec traverse v = if is_young v then try let visited = VarMap.find table v in if not visited then (* This node is in the table, but has not been fully visited. Hence, it is being visited. A cycle has been found. *) raise (Cycle v) with Not_found -> (* Mark this variable as being visited. *) VarMap.add table v false; (* Visit its successors. *) Option.iter (S.iter traverse) (structure v); (* Mark this variable as fully visited. *) VarMap.replace table v true in traverse (* -------------------------------------------------------------------------- *) (* Bottom-up computation over an acyclic graph. *) let new_acyclic_decoder (leaf : variable -> 'a) (fold : 'a structure -> 'a) : variable -> 'a = (* This hash table records the variables that have been visited and the value that has been computed for them. *) let visited : 'a VarMap.t = VarMap.create 128 in let rec decode v = try VarMap.find visited v with Not_found -> (* Because the graph is assumed to be acyclic, it is ok to update the hash table only after the recursive call. *) let a = match structure v with | None -> leaf v | Some t -> fold (S.map decode t) in VarMap.add visited v a; a in decode (* -------------------------------------------------------------------------- *) (* The cyclic decoder is designed mainly with the goal of constructing recursive types using [\mu] syntax. We must ensure that every use of a [\mu]-bound variable is dominated by its binder. This makes it impossible to use a table of [visited] nodes and share their value; we would risk entering an already-visited cycle via a different path. In order to avoid this problem, we define a decoder that uses a [visiting] table, but no [visited] table. This makes it correct, but potentially exponentially inefficient. Use with caution! *) (* This cyclic decoder doesn't have persistent state: the table is initially empty and finally empty. Two toplevel calls to the decoder with the same arguments produce the same results. *) (* A hash table records the variables that are being visited and also records whether they have been recursively re-discovered (i.e., they participate in a cycle). *) type status = (* this variable is being visited: *) | Active (* this variable is being visited and participates in a cycle: *) | Rediscovered let new_cyclic_decoder (leaf : variable -> 'a) (fold : 'a structure -> 'a) (mu : variable -> 'a -> 'a) : variable -> 'a = let table : status VarMap.t = VarMap.create 128 in let rec decode v = match structure v with | None -> (* Begin with the easy case where there is no structure. In this case, this variable cannot participate in a cycle. The table isn't needed. *) leaf v | Some t -> (* There is some structure [t]. *) if VarMap.mem table v then begin (* We have just rediscovered this variable. Change its status in the table (which could be [Active] or [Rediscovered]) to [Rediscovered], and stop the traversal. *) VarMap.replace table v Rediscovered; leaf v end else begin (* This variable is not being visited. Before the recursive call, mark it as being visited. *) VarMap.add table v Active; (* Perform the recursive traversal. *) let a = fold (S.map decode t) in (* Mark this variable as no longer being visited. If it was recursively rediscovered during the recursive call, then introduce a \mu binder. *) let status = try VarMap.find table v with Not_found -> assert false in VarMap.remove table v; match status with Active -> a | Rediscovered -> mu v a end in decode end