package inferno

  1. Overview
  2. Docs
A library for constraint-based Hindley-Milner type inference

Install

dune-project
 Dependency

Authors

Maintainers

Sources

archive.tar.gz
md5=cf37ba58410ca1e5e5462d51e4c4fb46
sha512=f96ad6bbf99482455afd8e8a9503357f21798698e6a2a4a8d385877db844ffebcef24f8cf82622c931831896088a9b98e37f4230839a3d03ec1c64fae2a39920

doc/src/inferno/Decoder.ml.html

Source file Decoder.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
(******************************************************************************)
(*                                                                            *)
(*                                  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 Signatures

module Make
  (S : OSTRUCTURE)
  (U : MUNIFIER with type 'a structure := 'a S.structure)
  (O : OUTPUT with type 'a structure := 'a S.structure)
= struct
open U

(* -------------------------------------------------------------------------- *)

(* Decoding a variable. *)

(* The public function [decode_variable] decodes a variable, which must have
   leaf structure. The decoded variable is determined by the unique identity
   of the variable [v]. *)

let decode_variable (v : variable) : O.tyvar =
  let data = U.get v in
  assert (S.is_leaf data);
  O.inject (S.id data)

(* The internal function [decode_id] decodes a variable. As above, this
   variable must have leaf structure, and the decoded variable is determined
   by the unique identity [id], which is already at hand. *)

(* [O.variable] is used to convert the result from [O.tyvar] to [O.ty]. *)

let decode_id (id : id) : O.ty =
  O.variable (O.inject id)

(* -------------------------------------------------------------------------- *)

(* An acyclic decoder performs a bottom-up computation over an acyclic graph. *)

let new_acyclic_decoder () : variable -> O.ty =

  (* This hash table records the equivalence classes that have been visited
     and the value that has been computed for them. *)

  let visited : (id, O.ty) Hashtbl.t = Hashtbl.create 128 in

  let rec decode (v :  variable) : O.ty =
    let data = get v in
    let id = S.id data in
    try
      Hashtbl.find visited id
    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 =
        if S.is_leaf data then
          decode_id id
        else
          O.structure (S.map decode data)
      in
      Hashtbl.add visited id a;
      a

  in
  decode

(* -------------------------------------------------------------------------- *)

(* The cyclic decoder is designed so as to be able to construct recursive
   types using μ syntax. We must ensure that every use of a μ-bound variable
   is dominated by its binder. *)

(* One cannot naively use a table of visited nodes, which maps every node to a
   decoded value (as done in the acyclic decoder above). Indeed, when an
   already-visited cycle is re-entered via a different path, we would risk
   producing a decoded value that contain variables that are meant to be
   μ-bound, but are actually unbound, because the μ binder lies along a
   different path. *)

(* Another naive approach would be to perform no memoization at all. This
   can work, but can be exponentially inefficient, because a DAG is decoded
   to a tree. *)

(* We choose to use a memoization table, [visited], but we memoize only the
   vertices that do not participate in a cycle. In practice, we expect cycles
   to be infrequent, so this approach should have good complexity. *)

(* The long-lived hash table [visited] maps the vertices that have been
   decoded already, and that do not participate in a cycle, to a decoded
   value. *)

(* When are asked to decode a vertex [v] that has not been memoized yet, we
   explore the graph formed by the vertices that are reachable from [v] and
   have not yet been memoized. We compute the strongly connected components
   of this graph. At a component of size 1, we memoize the decoded value. At
   a component of size greater than 1, we do not memoize it. The decoding
   process remains top-down, because (as far as I can tell) only a top-down
   process can tell us where μ binders must be placed. *)

(* -------------------------------------------------------------------------- *)

(* [isolated visited root] computes which unvisited vertices are reachable
   from [root]; computes the strongly connected components of this graph;
   and returns a function [isolated] of type [data -> bool] that
   indicates whether a vertex is the sole inhabitant of its component. *)

let isolated visited (root : variable) : data -> bool =

  (* A depth-first search lets us discover the unvisited vertices that
     are reachable from [root]. At the same time, we count them, and
     assign a unique index to each of them. *)

  let n = ref 0 in
  let discovered : (id, int) Hashtbl.t = Hashtbl.create 16 in
  let inhabitants : data list ref = ref [] in
  let rec discover v =
    let data = get v in
    let id = S.id data in
    if not (Hashtbl.mem visited id || Hashtbl.mem discovered id) then begin
      Hashtbl.add discovered id (Utils.postincrement n);
      inhabitants := data :: !inhabitants;
      S.iter discover data
    end
  in
  discover root;

  (* Then, we compute the strongly connected components of this graph. *)

  let module G = struct
    type node = data
    let n = !n
    let index data =
      try Hashtbl.find discovered (S.id data)
      with Not_found -> assert false
    let successors yield data =
      data |> S.iter (fun v ->
        let data = get v in
        if Hashtbl.mem discovered (S.id data) then yield data
      )
    let iter yield = List.iter yield !inhabitants
  end in
  let module T = Tarjan.Run(G) in
  T.isolated

(* -------------------------------------------------------------------------- *)

(* The short-lived hash table [table] (which is empty when the decoder is
   inactive) records which vertices are the target of a back edge and
   therefore need a μ binder. A vertex is mapped to [Active] when it is
   being visited. It is mapped to [Rediscovered] when it is being visited
   and a back edge to it has been discovered. This indicates that this
   vertex needs a μ binder. *)

type status =
  | Active
  | Rediscovered

(* -------------------------------------------------------------------------- *)

(* The cyclic decoder. *)

let new_cyclic_decoder () : variable -> O.ty =

  (* The long-lived table. *)
  let visited : (id, O.ty) Hashtbl.t = Hashtbl.create 128 in
  (* The short-lived table. *)
  let table : (id, status) Hashtbl.t = Hashtbl.create 128 in

  (* The toplevel decoding function. *)

  let decode (root : variable) : O.ty =

    (* If [root] appears in the table [visited], we are done. *)
    let data = get root in
    try
      Hashtbl.find visited (S.id data)
    with Not_found ->

      (* Otherwise, discover the unvisited vertices that are reachable from
         [root], and determine which of them are isolated, that is, which of
         them lie in a component of size 1. *)
      let isolated = isolated visited root in

      (* Then, the following recursive decoding function is used. The
         short-lived table [table] is used to detect back edges and
         determine where μ binders must be placed. *)

      let rec decode (v : variable) : O.ty =
        (* If [v] appears in the table [visited], we are done. *)
        let data = get v in
        let id = S.id data in
        try
          Hashtbl.find visited id
        with Not_found ->
          (* Otherwise, compute a decoded value [a] for this vertex. *)
          let a =
            if S.is_leaf data then
              (* There is no structure. *)
              decode_id id
            else
              (* There is some structure. *)
              if Hashtbl.mem table id then begin
                (* We have just rediscovered this vertex via a back edge. Its
                   status may be [Active] or [Rediscovered]. We change it to
                   [Rediscovered], and stop the traversal. *)
                Hashtbl.replace table id Rediscovered;
                decode_id id
              end
              else begin
                (* This vertex is not being visited. Before the recursive
                   call, mark it as being visited. *)
                Hashtbl.add table id Active;
                (* Perform the recursive traversal. *)
                let a = O.structure (S.map decode data) in
                (* Mark this vertex as no longer being visited. If it was
                   recursively rediscovered during the recursive call,
                   introduce a μ binder. (It would be correct but noisy to
                   always introduce a μ binder.) *)
                let status =
                  try Hashtbl.find table id with Not_found -> assert false
                in
                Hashtbl.remove table id;
                match status with
                | Active -> a
                | Rediscovered -> O.mu (O.inject id) a
              end
          in
          (* If this vertex is isolated, store the decoded value [a] in the
             memoization table [visited]. (It would be correct to never
             memoize. It would be incorrect to always memoize.) *)
          if isolated data then
            Hashtbl.add visited id a;
          a
      in

      assert (Hashtbl.length table = 0);
      let a = decode root in
      assert (Hashtbl.length table = 0);
      a

  in
  decode

(* -------------------------------------------------------------------------- *)

end (* Make *)
OCaml

Innovation. Community. Security.