Source file type_equal_intf.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
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
(** The purpose of [Type_equal] is to represent type equalities that the type checker
otherwise would not know, perhaps because the type equality depends on dynamic data,
or perhaps because the type system isn't powerful enough.
A value of type [(a, b) Type_equal.t] represents that types [a] and [b] are equal.
One can think of such a value as a proof of type equality. The [Type_equal] module
has operations for constructing and manipulating such proofs. For example, the
functions [refl], [sym], and [trans] express the usual properties of reflexivity,
symmetry, and transitivity of equality.
If one has a value [t : (a, b) Type_equal.t] that proves types [a] and [b] are equal,
there are two ways to use [t] to safely convert a value of type [a] to a value of type
[b]: [Type_equal.conv] or pattern matching on [Type_equal.T]:
{[
let f (type a) (type b) (t : (a, b) Type_equal.t) (a : a) : b =
Type_equal.conv t a
let f (type a) (type b) (t : (a, b) Type_equal.t) (a : a) : b =
let Type_equal.T = t in a
]}
At runtime, conversion by either means is just the identity -- nothing is changing
about the value. Consistent with this, a value of type [Type_equal.t] is always just
a constructor [Type_equal.T]; the value has no interesting semantic content.
[Type_equal] gets its power from the ability to, in a type-safe way, prove to the type
checker that two types are equal. The [Type_equal.t] value that is passed is
necessary for the type-checker's rules to be correct, but the compiler could, in
principle, not pass around values of type [Type_equal.t] at runtime.
*)
open! Import
open T
(**/**)
module Type_equal_defns (Type_equal : T.T2) = struct
(** The [Lift*] module types are used by the [Lift*] functors. See below. *)
module type Lift = sig
type 'a t
val lift : ('a, 'b) Type_equal.t -> ('a t, 'b t) Type_equal.t
end
module type Lift2 = sig
type ('a, 'b) t
val lift
: ('a1, 'b1) Type_equal.t
-> ('a2, 'b2) Type_equal.t
-> (('a1, 'a2) t, ('b1, 'b2) t) Type_equal.t
end
module type Lift3 = sig
type ('a, 'b, 'c) t
val lift
: ('a1, 'b1) Type_equal.t
-> ('a2, 'b2) Type_equal.t
-> ('a3, 'b3) Type_equal.t
-> (('a1, 'a2, 'a3) t, ('b1, 'b2, 'b3) t) Type_equal.t
end
(** [Injective] is an interface that states that a type is injective, where the type is
viewed as a function from types to other types. It predates OCaml's support for
explicit injectivity annotations in the type system.
The typical prior usage was:
{[
type 'a t
include Injective with type 'a t := 'a t
]}
For example, ['a list] is an injective type, because whenever ['a list = 'b list],
we know that ['a] = ['b]. On the other hand, if we define:
{[
type 'a t = unit
]}
then clearly [t] isn't injective, because, e.g., [int t = bool t], but
[int <> bool].
If [module M : Injective], then [M.strip] provides a way to get a proof that two
types are equal from a proof that both types transformed by [M.t] are equal. A
typical implementation looked like this:
{[
let strip (type a) (type b)
(Type_equal.T : (a t, b t) Type_equal.t) : (a, b) Type_equal.t =
Type_equal.T
]}
This will not type check for all type constructors (certainly not for non-injective
ones!), but it's always safe to try the above implementation if you are unsure. If
OCaml accepts this definition, then the type is injective. On the other hand, if
OCaml doesn't, then the type may or may not be injective. For example, if the
definition of the type depends on abstract types that match [Injective], OCaml will
not automatically use their injectivity, and one will have to write a more
complicated definition of [strip] that causes OCaml to use that fact. For example:
{[
module F (M : Type_equal.Injective) : Type_equal.Injective = struct
type 'a t = 'a M.t * int
let strip (type a) (type b)
(e : (a t, b t) Type_equal.t) : (a, b) Type_equal.t =
let e1, _ = Type_equal.detuple2 e in
M.strip e1
;;
end
]}
If in the definition of [F] we had written the simpler implementation of [strip] that
didn't use [M.strip], then OCaml would have reported a type error.
*)
module type Injective = sig
type 'a t
val strip : ('a t, 'b t) Type_equal.t -> ('a, 'b) Type_equal.t
end
[@@deprecated
"[since 2023-08] OCaml now supports injectivity annotations. [type !'a t] declares \
that ['a t] is injective with respect to ['a]."]
(** [Injective2] is for a binary type that is injective in both type arguments. *)
module type Injective2 = sig
type ('a1, 'a2) t
val strip
: (('a1, 'a2) t, ('b1, 'b2) t) Type_equal.t
-> ('a1, 'b1) Type_equal.t * ('a2, 'b2) Type_equal.t
end
[@@deprecated
"[since 2023-08] OCaml now supports injectivity annotations. [type !'a t] declares \
that ['a t] is injective with respect to ['a]."]
(** [Composition_preserves_injectivity] is a functor that proves that composition of
injective types is injective. *)
module Composition_preserves_injectivity (M1 : Injective) (M2 : Injective) :
Injective with type 'a t = 'a M1.t M2.t = struct
type 'a t = 'a M1.t M2.t
let strip e = M1.strip (M2.strip e)
end
[@@alert "-deprecated"]
[@@deprecated
"[since 2023-08] OCaml now supports injectivity annotations. [type !'a t] declares \
that ['a t] is injective with respect to ['a]."]
end
module Type_equal_id_defns (Id : sig
type 'a t
end) =
struct
module type Arg0 = sig
type t [@@deriving_inline sexp_of]
val sexp_of_t : t -> Sexplib0.Sexp.t
[@@@end]
val name : string
end
module type Arg1 = sig
type !'a t [@@deriving_inline sexp_of]
val sexp_of_t : ('a -> Sexplib0.Sexp.t) -> 'a t -> Sexplib0.Sexp.t
[@@@end]
val name : string
end
module type Arg2 = sig
type (!'a, !'b) t [@@deriving_inline sexp_of]
val sexp_of_t
: ('a -> Sexplib0.Sexp.t)
-> ('b -> Sexplib0.Sexp.t)
-> ('a, 'b) t
-> Sexplib0.Sexp.t
[@@@end]
val name : string
end
module type Arg3 = sig
type (!'a, !'b, !'c) t [@@deriving_inline sexp_of]
val sexp_of_t
: ('a -> Sexplib0.Sexp.t)
-> ('b -> Sexplib0.Sexp.t)
-> ('c -> Sexplib0.Sexp.t)
-> ('a, 'b, 'c) t
-> Sexplib0.Sexp.t
[@@@end]
val name : string
end
module type S0 = sig
type t
val type_equal_id : t Id.t
end
module type S1 = sig
type 'a t
val type_equal_id : 'a Id.t -> 'a t Id.t
end
module type S2 = sig
type ('a, 'b) t
val type_equal_id : 'a Id.t -> 'b Id.t -> ('a, 'b) t Id.t
end
module type S3 = sig
type ('a, 'b, 'c) t
val type_equal_id : 'a Id.t -> 'b Id.t -> 'c Id.t -> ('a, 'b, 'c) t Id.t
end
end
(**/**)
module type Type_equal = sig
type ('a, 'b) t = T : ('a, 'a) t [@@deriving_inline sexp_of]
val sexp_of_t
: ('a -> Sexplib0.Sexp.t)
-> ('b -> Sexplib0.Sexp.t)
-> ('a, 'b) t
-> Sexplib0.Sexp.t
[@@@end]
(** just an alias, needed when [t] gets shadowed below *)
type ('a, 'b) equal := ('a, 'b) t
(** @inline *)
include module type of Type_equal_defns (struct
type ('a, 'b) t = ('a, 'b) equal
end)
(** [refl], [sym], and [trans] construct proofs that type equality is reflexive,
symmetric, and transitive. *)
val refl : ('a, 'a) t
val sym : ('a, 'b) t -> ('b, 'a) t
val trans : ('a, 'b) t -> ('b, 'c) t -> ('a, 'c) t
(** [conv t x] uses the type equality [t : (a, b) t] as evidence to safely cast [x]
from type [a] to type [b]. [conv] is semantically just the identity function.
In a program that has [t : (a, b) t] where one has a value of type [a] that one
wants to treat as a value of type [b], it is often sufficient to pattern match on
[Type_equal.T] rather than use [conv]. However, there are situations where OCaml's
type checker will not use the type equality [a = b], and one must use [conv]. For
example:
{[
module F (M1 : sig type t end) (M2 : sig type t end) : sig
val f : (M1.t, M2.t) equal -> M1.t -> M2.t
end = struct
let f equal (m1 : M1.t) = conv equal m1
end
]}
If one wrote the body of [F] using pattern matching on [T]:
{[
let f (T : (M1.t, M2.t) equal) (m1 : M1.t) = (m1 : M2.t)
]}
this would give a type error. *)
val conv : ('a, 'b) t -> 'a -> 'b
(** It is always safe to conclude that if type [a] equals [b], then for any type ['a t],
type [a t] equals [b t]. The OCaml type checker uses this fact when it can. However,
sometimes, e.g., when using [conv], one needs to explicitly use this fact to
construct an appropriate [Type_equal.t]. The [Lift*] functors do this. *)
module Lift (T : T1) : Lift with type 'a t := 'a T.t
module Lift2 (T : T2) : Lift2 with type ('a, 'b) t := ('a, 'b) T.t
module Lift3 (T : T3) : Lift3 with type ('a, 'b, 'c) t := ('a, 'b, 'c) T.t
(** [tuple2] and [detuple2] convert between equality on a 2-tuple and its components. *)
val detuple2 : ('a1 * 'a2, 'b1 * 'b2) t -> ('a1, 'b1) t * ('a2, 'b2) t
val tuple2 : ('a1, 'b1) t -> ('a2, 'b2) t -> ('a1 * 'a2, 'b1 * 'b2) t
(** [Id] provides identifiers for types, and the ability to test (via [Id.same]) at
runtime if two identifiers are equal, and if so to get a proof of equality of their
types. Unlike values of type [Type_equal.t], values of type [Id.t] do have semantic
content and must have a nontrivial runtime representation. *)
module Id : sig
type 'a t [@@deriving_inline sexp_of]
val sexp_of_t : ('a -> Sexplib0.Sexp.t) -> 'a t -> Sexplib0.Sexp.t
[@@@end]
(** @inline *)
include module type of Type_equal_id_defns (struct
type nonrec 'a t = 'a t
end)
(** Every [Id.t] contains a unique id that is distinct from the [Uid.t] in any other
[Id.t]. *)
module Uid : sig
type t [@@deriving_inline hash, sexp_of]
include Ppx_hash_lib.Hashable.S with type t := t
val sexp_of_t : t -> Sexplib0.Sexp.t
[@@@end]
include Comparable.S with type t := t
end
val uid : _ t -> Uid.t
(** [create ~name] defines a new type identity. Two calls to [create] will result in
two distinct identifiers, even for the same arguments with the same type. If the
type ['a] doesn't support sexp conversion, then a good practice is to have the
converter be [[%sexp_of: _]], (or [sexp_of_opaque], if not using ppx_sexp_conv).
*)
val create : name:string -> ('a -> Sexp.t) -> 'a t
(** Accessors *)
val hash : _ t -> int
val name : _ t -> string
val to_sexp : 'a t -> 'a -> Sexp.t
val hash_fold_t : Hash.state -> _ t -> Hash.state
(** [same_witness t1 t2] and [same_witness_exn t1 t2] return a type equality proof iff
the two identifiers are the same (i.e., physically equal, resulting from the same
call to [create]). This is a useful way to achieve a sort of dynamic typing.
[same_witness] does not allocate a [Some] every time it is called.
[same t1 t2 = is_some (same_witness t1 t2)].
*)
val same : _ t -> _ t -> bool
val same_witness : 'a t -> 'b t -> ('a, 'b) equal option
val same_witness_exn : 'a t -> 'b t -> ('a, 'b) equal
module Create0 (T : Arg0) : S0 with type t := T.t
module Create1 (T : Arg1) : S1 with type 'a t := 'a T.t
module Create2 (T : Arg2) : S2 with type ('a, 'b) t := ('a, 'b) T.t
module Create3 (T : Arg3) : S3 with type ('a, 'b, 'c) t := ('a, 'b, 'c) T.t
end
end