-
Notifications
You must be signed in to change notification settings - Fork 8
/
equal.ml
329 lines (314 loc) · 19 KB
/
equal.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
open Util
open Reporter
open Dim
open Syntax
open Value
open Domvars
open Norm
open Act
open Monad.ZeroOps (Monad.Maybe)
open Bwd
module ListM = Mlist.Monadic (Monad.Maybe)
module BwdM = Mbwd.Monadic (Monad.Maybe)
(* Eta-expanding equality checks. In all functions, the integer is the current De Bruijn level, i.e. the length of the current context (we don't need any other information about the context). *)
module Mode = Algaeff.Reader.Make (struct
type t = [ `Rigid | `Full ]
end)
let () = Mode.register_printer (function `Read -> Some "unhandled Equal.Mode.read effect")
module Equal = struct
(* Compare two normal forms that are *assumed* to have the same type. *)
let rec equal_nf : int -> normal -> normal -> unit option =
fun n x y ->
(* Thus, we can do an eta-expanding check at either one of their stored types, since they are assumed equal. *)
equal_at n x.tm y.tm x.ty
(* Compare two values at a type, which they are both assumed to belong to. We do eta-expansion here if the type is one with an eta-rule, like a pi-type or a record type. We also deal with the case of terms that don't synthesize, such as structs even in codatatypes without eta, and constructors in datatypes. *)
and equal_at : int -> kinetic value -> kinetic value -> kinetic value -> unit option =
fun ctx x y ty ->
(* The type must be fully instantiated. *)
match view_type ty "equal_at" with
(* The only interesting thing here happens when the type is one with an eta-rule, such as a pi-type. *)
| Pi (_, doms, cods, tyargs) ->
let newargs, _ = dom_vars ctx doms in
let output = tyof_app cods tyargs newargs in
(* If both terms have the given pi-type, then when applied to variables of the domains, they will both have the computed output-type, so we can recurse back to eta-expanding equality at that type. *)
equal_at (ctx + 1) (apply_term x newargs) (apply_term y newargs) output
(* Codatatypes (without eta) don't need to be dealt with here, even though structs can't be compared synthesizingly, since codatatypes aren't actually inhabited by (kinetic) structs, only neutral terms that are equal to potential structs. In the case of record types with eta, if there is a nonidentity insertion outside, then the type isn't actually a record type, *but* it still has an eta-rule since it is *isomorphic* to a record type! Thus, instead of checking whether the insertion is the identity, we apply its inverse permutation to the terms being compared. And because we pass off to 'field' and 'tyof_field', we don't need to make explicit use of any of the other data here. *)
| Canonical (_, Codata { eta = Eta; fields; ins; _ }, _) ->
let (Perm_to p) = perm_of_ins ins in
let pinv = deg_of_perm (perm_inv p) in
let x, y, ty = (act_value x pinv, act_value y pinv, gact_ty None ty pinv) in
(* Now we take the projections and compare them at appropriate types. It suffices to use the fields of x when computing the types of the fields, since we proceed to check the fields for equality *in order* and thus by the time we are checking equality of any particular field of x and y, the previous fields of x and y are already known to be equal, and the type of the current field can only depend on these. (This latter is a semantic constraint on the kinds of generalized records that can sensibly admit eta-conversion.) *)
BwdM.miterM
(fun [ (fld, _) ] ->
equal_at ctx (field_term x fld) (field_term y fld) (tyof_field x ty fld))
[ fields ]
(* At a codatatype without eta, there are no kinetic structs, only comatches, and those are not compared componentwise, only as neutrals, since they are generative, so we don't need a clause for it. *)
(* At a higher-dimensional version of a discrete datatype, any two terms are equal. Note that we do not check here whether discreteness is on: that affects datatypes when they are *defined*, not when they are used. *)
| Canonical (_, Data { dim; discrete = `Yes; _ }, _) when is_pos dim -> return ()
(* At an ordinary datatype, two constructors are equal if they are instances of the same constructor, with the same dimension and arguments. We handle these cases here because we can use the datatype information to give types to the arguments of the constructor. *)
| Canonical (_, Data { constrs; _ }, tyargs) -> (
let x, y =
match Mode.read () with
| `Rigid -> (x, y)
| `Full -> (view_term x, view_term y) in
match (x, y) with
| Constr (xconstr, xn, xargs), Constr (yconstr, yn, yargs) -> (
let (Dataconstr { env; args = argtys; indices = _ }) =
match Abwd.find_opt xconstr constrs with
| Some x -> x
| None -> fatal (Anomaly "constr not found in equality-check") in
let* () = guard (xconstr = yconstr) in
match
( D.compare xn yn,
D.compare xn (TubeOf.inst tyargs),
D.compare (TubeOf.inst tyargs) (dim_env env) )
with
| Neq, _, _ -> fatal (Dimension_mismatch ("equality of constrs", xn, yn))
| _, Neq, _ ->
fatal (Dimension_mismatch ("equality of constrs", xn, TubeOf.inst tyargs))
| _, _, Neq ->
fatal
(Dimension_mismatch ("equality at canonical", TubeOf.inst tyargs, dim_env env))
| Eq, Eq, Eq ->
(* The instantiation must be at other instances of the same constructor; we take its arguments as in 'check'. *)
let tyarg_args =
TubeOf.mmap
{
map =
(fun _ [ tm ] ->
match view_term tm.tm with
| Constr (tmname, _, tmargs) ->
if tmname = xconstr then List.map (fun a -> CubeOf.find_top a) tmargs
else fatal (Anomaly "inst arg wrong constr in equality at datatype")
| _ -> fatal (Anomaly "inst arg not constr in equality at datatype"));
}
[ tyargs ] in
(* It suffices to compare the top-dimensional faces of the cubes; the others are only there for evaluating case trees. It would be nice to do this recursion directly on the Bwds, but equal_at_tel is expressed much more cleanly as an operation on lists. *)
equal_at_tel ctx env
(List.fold_right (fun a args -> CubeOf.find_top a :: args) xargs [])
(List.fold_right (fun a args -> CubeOf.find_top a :: args) yargs [])
argtys
(TubeOf.mmap { map = (fun _ [ args ] -> args) } [ tyarg_args ]))
| Constr _, _ | _, Constr _ -> fail
| _ -> equal_val ctx x y)
(* If the type is not one that has an eta-rule, then we pass off to a synthesizing equality-check, forgetting about our assumption that the two terms had the same type. This is the equality-checking analogue of the conversion rule for checking a synthesizing term, but since equality requires no evidence we don't have to actually synthesize a type at which they are equal or verify that it equals the type we assumed them to have. *)
| _ -> equal_val ctx x y
(* "Synthesizing" equality check of two values, now *not* assumed a priori to have the same type. If this function concludes that they are equal, then the equality of their types is part of that conclusion. *)
and equal_val : int -> kinetic value -> kinetic value -> unit option =
fun n x y ->
let x, y =
match Mode.read () with
| `Rigid -> (x, y)
| `Full -> (view_term x, view_term y) in
match (x, y) with
(* Since an Inst has a positive amount of instantiation, it can never equal an Uninst. We don't need to check that the types agree, since equal_uninst concludes equality of types rather than assumes it. *)
| Uninst (u, _), Uninst (v, _) -> equal_uninst n u v
| Inst { tm = tm1; dim = _; args = a1; tys = _ }, Inst { tm = tm2; dim = _; args = a2; tys = _ }
-> (
let* () = equal_uninst n tm1 tm2 in
(* If tm1 and tm2 are equal and have the same type, that type must be an instantiation of a universe of the same dimension, itself instantiated at the same arguments. So for the instantiations to be equal (including their types), it suffices for the instantiation dimensions and arguments to be equal. *)
match
( D.compare (TubeOf.inst a1) (TubeOf.inst a2),
D.compare (TubeOf.uninst a1) (TubeOf.uninst a2) )
with
| Eq, Eq ->
let Eq = D.plus_uniq (TubeOf.plus a1) (TubeOf.plus a2) in
let open TubeOf.Monadic (Monad.Maybe) in
(* Because instantiation arguments are stored as normals, we use type-sensitive equality to compare them. *)
miterM { it = (fun _ [ x; y ] -> equal_nf n x y) } [ a1; a2 ]
| _ -> fail)
| Lam _, _ | _, Lam _ -> fatal (Anomaly "unexpected lambda in synthesizing equality-check")
| Struct _, _ | _, Struct _ ->
fatal (Anomaly "unexpected struct in synthesizing equality-check")
| Constr _, _ | _, Constr _ ->
fatal (Anomaly "unexpected constr in synthesizing equality-check")
| _, _ -> fail
(* Subroutine of equal_val. Like it, equality of the types is part of the conclusion, not a hypothesis. *)
and equal_uninst : int -> uninst -> uninst -> unit option =
fun lvl x y ->
match (x, y) with
| UU m, UU n -> (
(* Two universes are equal precisely when they have the same dimension, in which case they also automatically have the same type (a standard instantiation of a (higher) universe of that same dimension). *)
match D.compare m n with
| Eq -> return ()
| _ -> fail)
| Neu { head = head1; args = args1; value = _ }, Neu { head = head2; args = args2; value = _ }
->
(* To check two neutral applications are equal, with their types, we first check if the functions are equal, including their types and hence also their domains and codomains (and also they have the same insertion applied outside). An alignment doesn't affect definitional equality. *)
let* () = equal_head lvl head1 head2 in
(* Then we recursively check that all their arguments are equal. *)
equal_args lvl args1 args2
| Pi (_, dom1s, cod1s), Pi (_, dom2s, cod2s) -> (
(* If two pi-types have the same dimension, equal domains, and equal codomains, they are equal and have the same type (an instantiation of the universe of that dimension at pi-types formed from the lower-dimensional domains and codomains). *)
let k = CubeOf.dim dom1s in
match D.compare (CubeOf.dim dom2s) k with
| Eq ->
let open CubeOf.Monadic (Monad.Maybe) in
let* () = miterM { it = (fun _ [ x; y ] -> equal_val lvl x y) } [ dom1s; dom2s ] in
(* We create variables for all the domains, in order to equality-check all the codomains. The codomain boundary types only use some of those variables, but it doesn't hurt to have the others around. *)
let newargs, _ = dom_vars lvl dom1s in
let open BindCube.Monadic (Monad.Maybe) in
miterM
{
it =
(fun s [ cod1; cod2 ] ->
let sargs = CubeOf.subcube s newargs in
equal_val (lvl + 1) (apply_binder_term cod1 sargs)
(apply_binder_term cod2 sargs));
}
[ cod1s; cod2s ]
| Neq -> fail)
| _ -> fail
(* Synthesizing equality check for heads. Again equality of types is part of the conclusion, not a hypothesis. *)
and equal_head : int -> head -> head -> unit option =
fun lvl x y ->
match (x, y) with
| Var { level = l1; deg = d1 }, Var { level = l2; deg = d2 } ->
(* Two equal variables with the same degeneracy applied are equal, including their types because that variable has only one type. *)
let* () = guard (l1 = l2) in
deg_equiv d1 d2
| Const { name = c1; ins = i1 }, Const { name = c2; ins = i2 } -> (
let* () = guard (c1 = c2) in
match D.compare (cod_left_ins i1) (cod_left_ins i2) with
| Eq ->
let To d1, To d2 = (deg_of_ins i1, deg_of_ins i2) in
deg_equiv d1 d2
| Neq -> fail)
| Meta { meta = meta1; env = env1; ins = i1 }, Meta { meta = meta2; env = env2; ins = i2 } -> (
match (Meta.compare meta1 meta2, D.compare (cod_left_ins i1) (cod_left_ins i2)) with
| Eq, Eq -> equal_env lvl env1 env2 (Global.find_meta meta1).termctx
| _ -> fail)
| _, _ -> fail
(* Check that the arguments of two entire application spines of equal functions are equal. This is basically a left fold, but we make sure to iterate from left to right, and fail rather than raising an exception if the lists have different lengths. *)
and equal_args : int -> app Bwd.t -> app Bwd.t -> unit option =
fun lvl args1 args2 ->
match (args1, args2) with
| Emp, Emp -> return ()
| Snoc (rest1, arg1), Snoc (rest2, arg2) ->
(* Iterating from left to right is important because it ensures that at the point of checking equality for any pair of arguments, we know that they have the same type, since they are valid arguments of equal functions with all previous arguments equal. *)
let* () = equal_args lvl rest1 rest2 in
equal_arg lvl arg1 arg2
| Emp, Snoc _ | Snoc _, Emp -> fail
(* Check that two application arguments are equal, including their outer insertions as well as their values. As noted above, here we can go back to *assuming* that they have equal types, and thus passing off to the eta-expanding equality check. *)
and equal_arg : int -> app -> app -> unit option =
fun n (App (a1, i1)) (App (a2, i2)) ->
let To d1, To d2 = (deg_of_ins i1, deg_of_ins i2) in
let* () = deg_equiv d1 d2 in
match (a1, a2) with
| Arg a1, Arg a2 -> (
match D.compare (CubeOf.dim a1) (CubeOf.dim a2) with
| Eq ->
let open CubeOf.Monadic (Monad.Maybe) in
miterM { it = (fun _ [ x; y ] -> (equal_nf n) x y) } [ a1; a2 ]
(* If the dimensions don't match, it is a bug rather than a user error, since they are supposed to both be valid arguments of the same function, and any function has a unique dimension. *)
| Neq ->
fatal
(Dimension_mismatch ("application in equality-check", CubeOf.dim a1, CubeOf.dim a2)))
| Field f1, Field f2 -> guard (f1 = f2)
| _, _ -> fail
and equal_at_tel :
type n a b ab.
int ->
(n, a) env ->
kinetic value list ->
kinetic value list ->
(a, b, ab) Telescope.t ->
(D.zero, n, n, kinetic value list) TubeOf.t ->
unit option =
fun ctx env xs ys tys tyargs ->
match (xs, ys, tys) with
| [], [], Emp -> Some ()
| x :: xs, y :: ys, Ext (_, ty, tys) ->
let ety = eval_term env ty in
(* Copied from check_tel; TODO: Factor it out *)
let tyargtbl = Hashtbl.create 10 in
let [ tyarg; tyargs ] =
TubeOf.pmap
{
map =
(fun fa [ tyargs ] ->
match tyargs with
| [] -> fatal (Anomaly "missing arguments in equal_at_tel")
| argtm :: argrest ->
let fa = sface_of_tface fa in
let argty =
inst
(eval_term (act_env env (op_of_sface fa)) ty)
(TubeOf.build D.zero
(D.zero_plus (dom_sface fa))
{
build =
(fun fb ->
Hashtbl.find tyargtbl
(SFace_of (comp_sface fa (sface_of_tface fb))));
}) in
let argnorm = { tm = argtm; ty = argty } in
Hashtbl.add tyargtbl (SFace_of fa) argnorm;
[ argnorm; argrest ]);
}
[ tyargs ] (Cons (Cons Nil)) in
let ity = inst ety tyarg in
let* () = equal_at ctx x y ity in
equal_at_tel ctx
(Ext
( env,
D.plus_zero (TubeOf.inst tyarg),
TubeOf.plus_cube (val_of_norm_tube tyarg) (CubeOf.singleton x) ))
xs ys tys tyargs
| _ -> fatal (Anomaly "length mismatch in equal_at_tel")
and equal_env : type a b n. int -> (n, b) env -> (n, b) env -> (a, b) Termctx.t -> unit option =
fun lvl env1 env2 (Permute (_, envctx)) -> equal_ordered_env lvl env1 env2 envctx
and equal_ordered_env :
type a b n. int -> (n, b) env -> (n, b) env -> (a, b) Termctx.Ordered.t -> unit option =
fun lvl env1 env2 envctx ->
(* Copied from readback_ordered_env *)
match envctx with
| Emp -> Some ()
| Lock envctx -> equal_ordered_env lvl env1 env2 envctx
| Snoc (envctx, entry, _) -> (
let open Monad.Ops (Monad.Maybe) in
let open CubeOf.Monadic (Monad.Maybe) in
let (Plus mk) = D.plus (Termctx.dim_entry entry) in
let (Looked_up { act = act1; op = Op (fc1, fd1); entry = xs1 }) =
lookup_cube env1 mk Now (id_op (dim_env env1)) in
let xs1 = act_cube { act = act1 } (CubeOf.subcube fc1 xs1) fd1 in
let (Looked_up { act = act2; op = Op (fc2, fd2); entry = xs2 }) =
lookup_cube env2 mk Now (id_op (dim_env env2)) in
let xs2 = act_cube { act = act2 } (CubeOf.subcube fc2 xs2) fd2 in
let env1' = remove_env env1 Now in
let env2' = remove_env env2 Now in
let* () = equal_ordered_env lvl env1' env2' envctx in
match entry with
| Vis { bindings; _ } | Invis bindings ->
let xtytbl = Hashtbl.create 10 in
let* _ =
mmapM
{
map =
(fun fab [ tm1; tm2 ] ->
let (SFace_of_plus (_, fb, fa)) = sface_of_plus mk fab in
let ty = (CubeOf.find bindings fa).ty in
let k = dom_sface fb in
let ty =
inst
(eval_term (act_env env1 (op_of_sface fb)) ty)
(TubeOf.build k (D.plus_zero k)
{
build =
(fun fc ->
Hashtbl.find xtytbl
(SFace_of (comp_sface fb (sface_of_tface fc))));
}) in
Hashtbl.add xtytbl (SFace_of fb) { tm = tm1; ty };
equal_at lvl tm1 tm2 ty);
}
[ xs1; xs2 ] in
return ())
end
let fallback f =
match Mode.run ~env:`Rigid f with
| None -> if GluedEval.read () then Mode.run ~env:`Full f else None
| Some () -> Some ()
let equal_at ctx x y ty = fallback @@ fun () -> Equal.equal_at ctx x y ty
let equal_val ctx x y = fallback @@ fun () -> Equal.equal_val ctx x y
let equal_arg ctx x y = fallback @@ fun () -> Equal.equal_arg ctx x y