3 Authors: Klaus Aehlig, LMU Muenchen; Tobias Nipkow, Florian Haftmann, TU Muenchen
5 Normalization by evaluation, based on generic code generator.
10 val norm_conv: cterm -> thm
11 val norm_term: theory -> term -> term
14 Const of int * Univ list (*named (uninterpreted) constants*)
15 | Free of string * Univ list (*free (uninterpreted) variables*)
16 | DFree of string * int (*free (uninterpreted) dictionary parameters*)
17 | BVar of int * Univ list
18 | Abs of (int * (Univ list -> Univ)) * Univ list;
19 val apps: Univ -> Univ list -> Univ (*explicit applications*)
20 val abss: int -> (Univ list -> Univ) -> Univ
21 (*abstractions as closures*)
23 val univs_ref: (unit -> Univ list -> Univ list) option ref
26 val setup: class list -> (string * string) list -> theory -> theory
32 (* generic non-sense *)
34 val trace = ref false;
35 fun tracing f x = if !trace then (Output.tracing (f x); x) else x;
38 (** the semantical universe **)
41 Functions are given by their semantical function value. To avoid
42 trouble with the ML-type system, these functions have the most
43 generic type, that is "Univ list -> Univ". The calling convention is
44 that the arguments come as a list, the last argument first. In
45 other words, a function call that usually would look like
47 f x_1 x_2 ... x_n or f(x_1,x_2, ..., x_n)
49 would be in our convention called as
53 Moreover, to handle functions that are still waiting for some
54 arguments we have additionally a list of arguments collected to far
55 and the number of arguments we're still waiting for.
59 Const of int * Univ list (*named (uninterpreted) constants*)
60 | Free of string * Univ list (*free variables*)
61 | DFree of string * int (*free (uninterpreted) dictionary parameters*)
62 | BVar of int * Univ list (*bound named variables*)
63 | Abs of (int * (Univ list -> Univ)) * Univ list
64 (*abstractions as closures*);
66 (* constructor functions *)
68 fun abss n f = Abs ((n, f), []);
69 fun apps (Abs ((n, f), xs)) ys = let val k = n - length ys in
70 if k = 0 then f (ys @ xs)
72 let val (zs, ws) = chop (~ k) ys
73 in apps (f (ws @ xs)) zs end
74 else Abs ((k, f), ys @ xs) end (*note: reverse convention also for apps!*)
75 | apps (Const (name, xs)) ys = Const (name, ys @ xs)
76 | apps (Free (name, xs)) ys = Free (name, ys @ xs)
77 | apps (BVar (name, xs)) ys = BVar (name, ys @ xs);
80 (** assembling and compiling ML code from terms **)
82 (* abstract ML syntax *)
85 fun e1 `$` e2 = "(" ^ e1 ^ " " ^ e2 ^ ")";
87 | e `$$` es = "(" ^ e ^ " " ^ space_implode " " es ^ ")";
88 fun ml_abs v e = "(fn " ^ v ^ " => " ^ e ^ ")";
91 "(case " ^ t ^ " of " ^ space_implode " | " (map (fn (p, t) => p ^ " => " ^ t) cs) ^ ")";
92 fun ml_Let d e = "let\n" ^ d ^ " in " ^ e ^ " end";
94 fun ml_list es = "[" ^ commas es ^ "]";
96 fun ml_fundefs ([(name, [([], e)])]) =
97 "val " ^ name ^ " = " ^ e ^ "\n"
98 | ml_fundefs (eqs :: eqss) =
100 fun fundef (name, eqs) =
102 fun eqn (es, e) = name ^ " " ^ space_implode " " es ^ " = " ^ e
103 in space_implode "\n | " (map eqn eqs) end;
105 (prefix "fun " o fundef) eqs :: map (prefix "and " o fundef) eqss
106 |> space_implode "\n"
110 (* nbe specific syntax and sandbox communication *)
112 val univs_ref = ref (NONE : (unit -> Univ list -> Univ list) option);
116 val name_ref = prefix ^ "univs_ref";
117 val name_const = prefix ^ "Const";
118 val name_abss = prefix ^ "abss";
119 val name_apps = prefix ^ "apps";
122 val univs_cookie = (name_ref, univs_ref);
124 fun nbe_fun "" = "nbe_value"
125 | nbe_fun c = "c_" ^ translate_string (fn "." => "_" | c => c) c;
126 fun nbe_dict v n = "d_" ^ v ^ "_" ^ string_of_int n;
127 fun nbe_bound v = "v_" ^ v;
129 (*note: these three are the "turning spots" where proper argument order is established!*)
130 fun nbe_apps t [] = t
131 | nbe_apps t ts = name_apps `$$` [t, ml_list (rev ts)];
132 fun nbe_apps_local c ts = nbe_fun c `$` ml_list (rev ts);
133 fun nbe_apps_constr idx ts =
134 name_const `$` ("(" ^ string_of_int idx ^ ", " ^ ml_list (rev ts) ^ ")");
136 fun nbe_abss 0 f = f `$` ml_list []
137 | nbe_abss n f = name_abss `$$` [string_of_int n, f];
141 open BasicCodeThingol;
143 (* code generation *)
145 fun assemble_eqnss idx_of deps eqnss =
147 fun prep_eqns (c, (vs, eqns)) =
149 val dicts = maps (fn (v, sort) => map_index (nbe_dict v o fst) sort) vs;
150 val num_args = length dicts + (length o fst o hd) eqns;
151 in (c, (num_args, (dicts, eqns))) end;
152 val eqnss' = map prep_eqns eqnss;
154 fun assemble_constapp c dss ts =
156 val ts' = (maps o map) assemble_idict dss @ ts;
157 in case AList.lookup (op =) eqnss' c
158 of SOME (n, _) => if n <= length ts'
159 then let val (ts1, ts2) = chop n ts'
160 in nbe_apps (nbe_apps_local c ts1) ts2
161 end else nbe_apps (nbe_abss n (nbe_fun c)) ts'
162 | NONE => if member (op =) deps c
163 then nbe_apps (nbe_fun c) ts'
164 else nbe_apps_constr (idx_of c) ts'
166 and assemble_idict (DictConst (inst, dss)) =
167 assemble_constapp inst dss []
168 | assemble_idict (DictVar (supers, (v, (n, _)))) =
169 fold_rev (fn super => assemble_constapp super [] o single) supers (nbe_dict v n);
171 fun assemble_iterm constapp =
175 val (t', ts) = CodeThingol.unfold_app t
176 in of_iapp t' (fold_rev (cons o of_iterm) ts []) end
177 and of_iapp (IConst (c, (dss, _))) ts = constapp c dss ts
178 | of_iapp (IVar v) ts = nbe_apps (nbe_bound v) ts
179 | of_iapp ((v, _) `|-> t) ts =
180 nbe_apps (nbe_abss 1 (ml_abs (ml_list [nbe_bound v]) (of_iterm t))) ts
181 | of_iapp (ICase (((t, _), cs), t0)) ts =
182 nbe_apps (ml_cases (of_iterm t) (map (pairself of_iterm) cs
183 @ [("_", of_iterm t0)])) ts
186 fun assemble_eqns (c, (num_args, (dicts, eqns))) =
188 val assemble_arg = assemble_iterm
189 (fn c => fn _ => fn ts => nbe_apps_constr (idx_of c) ts);
190 val assemble_rhs = assemble_iterm assemble_constapp;
191 fun assemble_eqn (args, rhs) =
192 ([ml_list (rev (dicts @ map assemble_arg args))], assemble_rhs rhs);
193 val default_args = map nbe_bound (Name.invent_list [] "a" num_args);
194 val default_eqn = if c = "" then NONE
195 else SOME ([ml_list (rev default_args)],
196 nbe_apps_constr (idx_of c) default_args);
198 ((nbe_fun c, map assemble_eqn eqns @ the_list default_eqn),
199 nbe_abss num_args (nbe_fun c))
202 val (fun_vars, fun_vals) = map_split assemble_eqns eqnss';
203 val deps_vars = ml_list (map nbe_fun deps);
204 in ml_abs deps_vars (ml_Let (ml_fundefs fun_vars) (ml_list fun_vals)) end;
206 (* code compilation *)
208 fun compile_eqnss gr raw_deps [] = []
209 | compile_eqnss gr raw_deps eqnss =
211 val (deps, deps_vals) = split_list (map_filter
212 (fn dep => Option.map (fn univ => (dep, univ)) (fst ((Graph.get_node gr dep)))) raw_deps);
213 val idx_of = raw_deps
214 |> map (fn dep => (dep, snd (Graph.get_node gr dep)))
215 |> AList.lookup (op =)
216 |> (fn f => the o f);
217 val s = assemble_eqnss idx_of deps eqnss;
218 val cs = map fst eqnss;
221 |> tracing (fn s => "\n--- code to be evaluated:\n" ^ s)
222 |> ML_Context.evaluate
223 (Output.tracing o enclose "\n---compiler echo:\n" "\n---\n",
224 Output.tracing o enclose "\n--- compiler echo (with error):\n" "\n---\n")
225 (!trace) univs_cookie
226 |> (fn f => f deps_vals)
227 |> (fn univs => cs ~~ univs)
230 (* preparing function equations *)
232 fun eqns_of_stmt (_, CodeThingol.Fun (_, [])) =
234 | eqns_of_stmt (const, CodeThingol.Fun ((vs, _), eqns)) =
235 [(const, (vs, map fst eqns))]
236 | eqns_of_stmt (_, CodeThingol.Datatypecons _) =
238 | eqns_of_stmt (_, CodeThingol.Datatype _) =
240 | eqns_of_stmt (class, CodeThingol.Class (v, (superclasses, classops))) =
242 val names = map snd superclasses @ map fst classops;
243 val params = Name.invent_list [] "d" (length names);
246 [([IConst (class, ([], [])) `$$ map IVar params], IVar (nth params k))]));
247 in map_index mk names end
248 | eqns_of_stmt (_, CodeThingol.Classrel _) =
250 | eqns_of_stmt (_, CodeThingol.Classparam _) =
252 | eqns_of_stmt (inst, CodeThingol.Classinst ((class, (_, arities)), (superinsts, instops))) =
253 [(inst, (arities, [([], IConst (class, ([], [])) `$$
254 map (fn (_, (_, (inst, dicts))) => IConst (inst, (dicts, []))) superinsts
255 @ map (IConst o snd o fst) instops)]))];
257 fun compile_stmts stmts_deps =
259 val names = map (fst o fst) stmts_deps;
260 val names_deps = map (fn ((name, _), deps) => (name, deps)) stmts_deps;
261 val eqnss = maps (eqns_of_stmt o fst) stmts_deps;
262 val refl_deps = names_deps
265 |> fold (insert (op =)) names;
266 fun new_node name (gr, (maxidx, idx_tab)) = if can (Graph.get_node gr) name
267 then (gr, (maxidx, idx_tab))
268 else (Graph.new_node (name, (NONE, maxidx)) gr,
269 (maxidx + 1, Inttab.update_new (maxidx, name) idx_tab));
270 fun compile gr = eqnss
271 |> compile_eqnss gr refl_deps
274 fold new_node refl_deps
275 #> apfst (fold (fn (name, deps) => fold (curry Graph.add_edge name) deps) names_deps
277 #-> fold (fn (name, univ) => (Graph.map_node name o apfst) (K (SOME univ))))
280 fun ensure_stmts code =
282 fun add_stmts names (gr, (maxidx, idx_tab)) = if exists ((can o Graph.get_node) gr) names
283 then (gr, (maxidx, idx_tab))
284 else (gr, (maxidx, idx_tab))
285 |> compile_stmts (map (fn name => ((name, Graph.get_node code name),
286 Graph.imm_succs code name)) names);
287 in fold_rev add_stmts (Graph.strong_conn code) end;
292 (* term evaluation *)
294 fun eval_term gr deps ((vs, ty), t) =
296 val frees = CodeThingol.fold_unbound_varnames (insert (op =)) t []
297 val frees' = map (fn v => Free (v, [])) frees;
298 val dict_frees = maps (fn (v, sort) => map_index (curry DFree v o fst) sort) vs;
300 ("", (vs, [(map IVar frees, t)]))
301 |> singleton (compile_eqnss gr deps)
303 |> (fn t => apps t (rev (dict_frees @ frees')))
308 fun term_of_univ thy idx_tab t =
310 fun take_until f [] = []
311 | take_until f (x::xs) = if f x then [] else x :: take_until f xs;
312 fun is_dict (Const (idx, _)) =
314 val c = the (Inttab.lookup idx_tab idx);
316 (is_some o CodeName.class_rev thy) c
317 orelse (is_some o CodeName.classrel_rev thy) c
318 orelse (is_some o CodeName.instance_rev thy) c
320 | is_dict (DFree _) = true
322 fun of_apps bounds (t, ts) =
323 fold_map (of_univ bounds) ts
324 #>> (fn ts' => list_comb (t, rev ts'))
325 and of_univ bounds (Const (idx, ts)) typidx =
327 val ts' = take_until is_dict ts;
328 val c = (the o CodeName.const_rev thy o the o Inttab.lookup idx_tab) idx;
329 val T = Code.default_typ thy c;
330 val T' = map_type_tvar (fn ((v, i), S) => TypeInfer.param (typidx + i) (v, [])) T;
331 val typidx' = typidx + maxidx_of_typ T' + 1;
332 in of_apps bounds (Term.Const (c, T'), ts') typidx' end
333 | of_univ bounds (Free (name, ts)) typidx =
334 of_apps bounds (Term.Free (name, dummyT), ts) typidx
335 | of_univ bounds (BVar (name, ts)) typidx =
336 of_apps bounds (Bound (bounds - name - 1), ts) typidx
337 | of_univ bounds (t as Abs _) typidx =
339 |> of_univ (bounds + 1) (apps t [BVar (bounds, [])])
340 |-> (fn t' => pair (Term.Abs ("u", dummyT, t')))
341 in of_univ 0 t 0 |> fst end;
345 structure Nbe_Functions = CodeDataFun
347 type T = (Univ option * int) Graph.T * (int * string Inttab.table);
348 val empty = (Graph.empty, (0, Inttab.empty));
349 fun merge _ ((gr1, (maxidx1, idx_tab1)), (gr2, (maxidx2, idx_tab2))) =
350 (Graph.merge (K true) (gr1, gr2), (IntInf.max (maxidx1, maxidx2),
351 Inttab.merge (K true) (idx_tab1, idx_tab2)));
352 fun purge _ NONE _ = empty
353 | purge NONE _ _ = empty
354 | purge (SOME thy) (SOME cs) (gr, (maxidx, idx_tab)) =
357 map_filter (CodeName.const_rev thy) (Graph.keys gr);
358 val dels = (Graph.all_preds gr
359 o map (CodeName.const thy)
360 o filter (member (op =) cs_exisiting)
362 in (Graph.del_nodes dels gr, (maxidx, idx_tab)) end;
365 (* compilation, evaluation and reification *)
367 fun compile_eval thy code vs_ty_t deps =
369 val (gr, (_, idx_tab)) = Nbe_Functions.change thy (ensure_stmts code);
373 |> term_of_univ thy idx_tab
376 (* trivial type classes *)
378 structure Nbe_Triv_Classes = TheoryDataFun
380 type T = class list * (string * string) list;
381 val empty = ([], []);
384 fun merge _ ((classes1, consts1), (classes2, consts2)) =
385 (Library.merge (op =) (classes1, classes2), Library.merge (op =) (consts1, consts2));
388 fun add_triv_classes thy =
390 val (trivs, _) = Nbe_Triv_Classes.get thy;
391 val inters = curry (Sorts.inter_sort (Sign.classes_of thy)) trivs;
392 fun map_sorts f = (map_types o map_atyps)
393 (fn TVar (v, sort) => TVar (v, f sort)
394 | TFree (v, sort) => TFree (v, f sort));
395 in map_sorts inters end;
397 fun subst_triv_consts thy =
399 fun subst_const f = map_aterms (fn t as Term.Const (c, ty) => (case f c
400 of SOME c' => Term.Const (c', ty)
403 val (_, consts) = Nbe_Triv_Classes.get thy;
404 val subst_inst = perhaps (Option.map fst o AxClass.inst_of_param thy);
405 in map_aterms (subst_const (AList.lookup (op =) consts o subst_inst)) end;
407 (* evaluation with type reconstruction *)
409 fun eval thy t code vs_ty_t deps =
412 val type_free = AList.lookup (op =)
413 (map (fn (s, T) => (s, Term.Free (s, T))) (Term.add_frees t []));
414 val type_frees = Term.map_aterms
415 (fn (t as Term.Free (s, _)) => the_default t (type_free s) | t => t);
416 fun type_infer t = [(t, ty)]
417 |> TypeInfer.infer_types (Sign.pp thy) (Sign.tsig_of thy) I
418 (try (Type.strip_sorts o Sign.the_const_type thy)) (K NONE)
420 |> fst |> the_single |> fst;
421 fun check_tvars t = if null (Term.term_tvars t) then t else
422 error ("Illegal schematic type variables in normalized term: "
423 ^ setmp show_types true (Sign.string_of_term thy) t);
424 val string_of_term = setmp show_types true (Sign.string_of_term thy);
426 compile_eval thy code vs_ty_t deps
427 |> tracing (fn t => "Normalized:\n" ^ string_of_term t)
428 |> subst_triv_consts thy
430 |> tracing (fn t => "Vars typed:\n" ^ string_of_term t)
432 |> tracing (fn t => "Types inferred:\n" ^ string_of_term t)
434 |> tracing (fn t => "---\n")
437 (* evaluation oracle *)
439 exception Norm of term * CodeThingol.code
440 * (CodeThingol.typscheme * CodeThingol.iterm) * string list;
442 fun norm_oracle (thy, Norm (t, code, vs_ty_t, deps)) =
443 Logic.mk_equals (t, eval thy t code vs_ty_t deps);
445 fun norm_invoke thy t code vs_ty_t deps =
446 Thm.invoke_oracle_i thy "HOL.norm" (thy, Norm (t, code, vs_ty_t, deps));
447 (*FIXME get rid of hardwired theory name*)
451 val thy = Thm.theory_of_cterm ct;
452 fun evaluator' t code vs_ty_t deps = norm_invoke thy t code vs_ty_t deps;
453 fun evaluator t = (add_triv_classes thy t, evaluator' t);
454 in CodePackage.evaluate_conv thy evaluator ct end;
456 fun norm_term thy t =
458 fun evaluator' t code vs_ty_t deps = eval thy t code vs_ty_t deps;
459 fun evaluator t = (add_triv_classes thy t, evaluator' t);
460 in (Code.postprocess_term thy o CodePackage.evaluate_term thy evaluator) t end;
462 (* evaluation command *)
464 fun norm_print_term ctxt modes t =
466 val thy = ProofContext.theory_of ctxt;
467 val t' = norm_term thy t;
468 val ty' = Term.type_of t';
469 val p = PrintMode.with_modes modes (fn () =>
470 Pretty.block [Pretty.quote (Syntax.pretty_term ctxt t'), Pretty.fbrk,
471 Pretty.str "::", Pretty.brk 1, Pretty.quote (Syntax.pretty_typ ctxt ty')]) ();
472 in Pretty.writeln p end;
477 fun norm_print_term_cmd (modes, s) state =
478 let val ctxt = Toplevel.context_of state
479 in norm_print_term ctxt modes (Syntax.read_term ctxt s) end;
481 fun setup nbe_classes nbe_consts =
482 Theory.add_oracle ("norm", norm_oracle)
483 #> Nbe_Triv_Classes.map (K (nbe_classes, nbe_consts));
485 local structure P = OuterParse and K = OuterKeyword in
487 val opt_modes = Scan.optional (P.$$$ "(" |-- P.!!! (Scan.repeat1 P.xname --| P.$$$ ")")) [];
490 OuterSyntax.improper_command "normal_form" "normalize term by evaluation" K.diag
491 (opt_modes -- P.typ >> (Toplevel.keep o norm_print_term_cmd));