src/HOL/Tools/Sledgehammer/sledgehammer_mash.ML
changeset 48406 b002cc16aa99
parent 48404 0a261b4aa093
child 48407 47fe0ca12fc2
equal deleted inserted replaced
48405:7682bc885e8a 48406:b002cc16aa99
    26   val fact_filters : string list
    26   val fact_filters : string list
    27   val escape_meta : string -> string
    27   val escape_meta : string -> string
    28   val escape_metas : string list -> string
    28   val escape_metas : string list -> string
    29   val unescape_meta : string -> string
    29   val unescape_meta : string -> string
    30   val unescape_metas : string -> string list
    30   val unescape_metas : string -> string list
    31   val extract_query : string -> string * string list
    31   val extract_query : string -> string * (string * real) list
    32   val nickname_of : thm -> string
    32   val nickname_of : thm -> string
    33   val suggested_facts : string list -> ('a * thm) list -> ('a * thm) list
    33   val suggested_facts :
       
    34     (string * 'a) list -> ('b * thm) list -> (('b * thm) * 'a) list
    34   val mesh_facts :
    35   val mesh_facts :
    35     int -> (('a * thm) list * ('a * thm) list) list -> ('a * thm) list
    36     int -> ((('a * thm) * real) list * ('a * thm) list) list -> ('a * thm) list
    36   val is_likely_tautology_or_too_meta : thm -> bool
       
    37   val theory_ord : theory * theory -> order
    37   val theory_ord : theory * theory -> order
    38   val thm_ord : thm * thm -> order
    38   val thm_ord : thm * thm -> order
    39   val goal_of_thm : theory -> thm -> thm
    39   val goal_of_thm : theory -> thm -> thm
    40   val run_prover_for_mash :
    40   val run_prover_for_mash :
    41     Proof.context -> params -> string -> fact list -> thm -> prover_result
    41     Proof.context -> params -> string -> fact list -> thm -> prover_result
    50     Proof.context -> bool
    50     Proof.context -> bool
    51     -> (string * string list * string list * string list) list -> unit
    51     -> (string * string list * string list * string list) list -> unit
    52   val mash_REPROVE :
    52   val mash_REPROVE :
    53     Proof.context -> bool -> (string * string list) list -> unit
    53     Proof.context -> bool -> (string * string list) list -> unit
    54   val mash_QUERY :
    54   val mash_QUERY :
    55     Proof.context -> bool -> int -> string list * string list -> string list
    55     Proof.context -> bool -> int -> string list * string list
       
    56     -> (string * real) list
    56   val mash_unlearn : Proof.context -> unit
    57   val mash_unlearn : Proof.context -> unit
    57   val mash_could_suggest_facts : unit -> bool
    58   val mash_could_suggest_facts : unit -> bool
    58   val mash_can_suggest_facts : Proof.context -> bool
    59   val mash_can_suggest_facts : Proof.context -> bool
    59   val mash_suggest_facts :
    60   val mash_suggested_facts :
    60     Proof.context -> params -> string -> int -> term list -> term
    61     Proof.context -> params -> string -> int -> term list -> term
    61     -> ('a * thm) list -> ('a * thm) list * ('a * thm) list
    62     -> ('a * thm) list -> (('a * thm) * real) list * ('a * thm) list
    62   val mash_learn_proof :
    63   val mash_learn_proof :
    63     Proof.context -> params -> string -> term -> ('a * thm) list -> thm list
    64     Proof.context -> params -> string -> term -> ('a * thm) list -> thm list
    64     -> unit
    65     -> unit
    65   val mash_learn :
    66   val mash_learn :
    66     Proof.context -> params -> fact_override -> thm list -> bool -> unit
    67     Proof.context -> params -> fact_override -> thm list -> bool -> unit
   130 val escape_metas = map escape_meta #> space_implode " "
   131 val escape_metas = map escape_meta #> space_implode " "
   131 val unescape_meta = String.explode #> unmeta_chars []
   132 val unescape_meta = String.explode #> unmeta_chars []
   132 val unescape_metas =
   133 val unescape_metas =
   133   space_explode " " #> filter_out (curry (op =) "") #> map unescape_meta
   134   space_explode " " #> filter_out (curry (op =) "") #> map unescape_meta
   134 
   135 
       
   136 fun extract_node line =
       
   137   case space_explode ":" line of
       
   138     [name, parents] => (unescape_meta name, unescape_metas parents)
       
   139   | _ => ("", [])
       
   140 
       
   141 fun extract_suggestion sugg =
       
   142   case space_explode "=" sugg of
       
   143     [name, weight] =>
       
   144     SOME (unescape_meta name, Real.fromString weight |> the_default 0.0)
       
   145   | _ => NONE
       
   146 
   135 fun extract_query line =
   147 fun extract_query line =
   136   case space_explode ":" line of
   148   case space_explode ":" line of
   137     [goal_name, suggs] => (unescape_meta goal_name, unescape_metas suggs)
   149     [goal, suggs] =>
       
   150     (unescape_meta goal,
       
   151      map_filter extract_suggestion (space_explode " " suggs))
   138   | _ => ("", [])
   152   | _ => ("", [])
   139 
   153 
   140 fun parent_of_local_thm th =
   154 fun parent_of_local_thm th =
   141   let
   155   let
   142     val thy = th |> Thm.theory_of_thm
   156     val thy = th |> Thm.theory_of_thm
   163 
   177 
   164 fun suggested_facts suggs facts =
   178 fun suggested_facts suggs facts =
   165   let
   179   let
   166     fun add_fact (fact as (_, th)) = Symtab.default (nickname_of th, fact)
   180     fun add_fact (fact as (_, th)) = Symtab.default (nickname_of th, fact)
   167     val tab = Symtab.empty |> fold add_fact facts
   181     val tab = Symtab.empty |> fold add_fact facts
   168   in map_filter (Symtab.lookup tab) suggs end
   182     fun find_sugg (name, weight) =
   169 
   183       Symtab.lookup tab name |> Option.map (rpair weight)
   170 (* Ad hoc score function roughly based on Blanchette's Ringberg 2011 data. *)
   184   in map_filter find_sugg suggs end
   171 fun score x = Math.pow (1.5, 15.5 - 0.05 * Real.fromInt x) + 15.0
   185 
   172 
   186 fun sum_avg [] = 0
   173 fun sum_sq_avg [] = 0
   187   | sum_avg xs = Real.ceil (100000.0 * fold (curry (op +)) xs 0.0) div length xs
   174   | sum_sq_avg xs =
   188 
   175     Real.ceil (100000.0 * fold (curry (op +) o score) xs 0.0) div length xs
   189 fun normalize_scores [] = []
   176 
   190   | normalize_scores ((fact, score) :: tail) =
   177 fun mesh_facts max_facts [(selected, unknown)] =
   191     (fact, 1.0) :: map (apsnd (curry Real.* (1.0 / score))) tail
   178     take max_facts selected @ take (max_facts - length selected) unknown
   192 
       
   193 fun mesh_facts max_facts [(sels, unks)] =
       
   194     map fst (take max_facts sels) @ take (max_facts - length sels) unks
   179   | mesh_facts max_facts mess =
   195   | mesh_facts max_facts mess =
   180     let
   196     let
   181       val mess = mess |> map (apfst (`length))
   197       val mess = mess |> map (apfst (normalize_scores #> `length))
   182       val fact_eq = Thm.eq_thm o pairself snd
   198       val fact_eq = Thm.eq_thm o pairself snd
       
   199       fun score_at sels = try (nth sels) #> Option.map snd
   183       fun score_in fact ((sel_len, sels), unks) =
   200       fun score_in fact ((sel_len, sels), unks) =
   184         case find_index (curry fact_eq fact) sels of
   201         case find_index (curry fact_eq fact o fst) sels of
   185           ~1 => (case find_index (curry fact_eq fact) unks of
   202           ~1 => (case find_index (curry fact_eq fact) unks of
   186                    ~1 => SOME sel_len
   203                    ~1 => score_at sels sel_len
   187                  | _ => NONE)
   204                  | _ => NONE)
   188         | j => SOME j
   205         | rank => score_at sels rank
   189       fun score_of fact = mess |> map_filter (score_in fact) |> sum_sq_avg
   206       fun weight_of fact = mess |> map_filter (score_in fact) |> sum_avg
   190       val facts = fold (union fact_eq o take max_facts o snd o fst) mess []
   207       val facts =
       
   208         fold (union fact_eq o map fst o take max_facts o snd o fst) mess []
   191     in
   209     in
   192       facts |> map (`score_of) |> sort (int_ord o swap o pairself fst)
   210       facts |> map (`weight_of) |> sort (int_ord o swap o pairself fst)
   193             |> map snd |> take max_facts
   211             |> map snd |> take max_facts
   194     end
   212     end
   195 
   213 
   196 val thy_feature_name_of = prefix "y"
   214 val thy_feature_name_of = prefix "y"
   197 val const_name_of = prefix "c"
   215 val const_name_of = prefix "c"
   198 val type_name_of = prefix "t"
   216 val type_name_of = prefix "t"
   199 val class_name_of = prefix "s"
   217 val class_name_of = prefix "s"
   200 
       
   201 fun is_likely_tautology_or_too_meta th =
       
   202   let
       
   203     val is_boring_const = member (op =) atp_widely_irrelevant_consts
       
   204     fun is_boring_bool t =
       
   205       not (exists_Const (not o is_boring_const o fst) t) orelse
       
   206       exists_type (exists_subtype (curry (op =) @{typ prop})) t
       
   207     fun is_boring_prop (@{const Trueprop} $ t) = is_boring_bool t
       
   208       | is_boring_prop (@{const "==>"} $ t $ u) =
       
   209         is_boring_prop t andalso is_boring_prop u
       
   210       | is_boring_prop (Const (@{const_name all}, _) $ (Abs (_, _, t)) $ u) =
       
   211         is_boring_prop t andalso is_boring_prop u
       
   212       | is_boring_prop (Const (@{const_name "=="}, _) $ t $ u) =
       
   213         is_boring_bool t andalso is_boring_bool u
       
   214       | is_boring_prop _ = true
       
   215   in
       
   216     is_boring_prop (prop_of th) andalso not (Thm.eq_thm_prop (@{thm ext}, th))
       
   217   end
       
   218 
   218 
   219 fun theory_ord p =
   219 fun theory_ord p =
   220   if Theory.eq_thy p then
   220   if Theory.eq_thy p then
   221     EQUAL
   221     EQUAL
   222   else if Theory.subthy p then
   222   else if Theory.subthy p then
   278         case strip_comb t of
   278         case strip_comb t of
   279           (Const (x as (s, _)), args) =>
   279           (Const (x as (s, _)), args) =>
   280           if is_bad_const x args then ""
   280           if is_bad_const x args then ""
   281           else mk_app (const_name_of s) (map (patternify (depth - 1)) args)
   281           else mk_app (const_name_of s) (map (patternify (depth - 1)) args)
   282         | _ => ""
   282         | _ => ""
       
   283     fun add_pattern depth t =
       
   284       case patternify depth t of "" => I | s => insert (op =) s
   283     fun add_term_patterns ~1 _ = I
   285     fun add_term_patterns ~1 _ = I
   284       | add_term_patterns depth t =
   286       | add_term_patterns depth t =
   285         insert (op =) (patternify depth t)
   287         add_pattern depth t #> add_term_patterns (depth - 1) t
   286         #> add_term_patterns (depth - 1) t
       
   287     val add_term = add_term_patterns term_max_depth
   288     val add_term = add_term_patterns term_max_depth
   288     fun add_patterns t =
   289     fun add_patterns t =
   289       let val (head, args) = strip_comb t in
   290       let val (head, args) = strip_comb t in
   290         (case head of
   291         (case head of
   291            Const (_, T) => add_term t #> add_type T
   292            Const (_, T) => add_term t #> add_type T
   325 val atp_dependency_default_max_fact = 50
   326 val atp_dependency_default_max_fact = 50
   326 
   327 
   327 fun trim_dependencies deps =
   328 fun trim_dependencies deps =
   328   if length deps <= max_dependencies then SOME deps else NONE
   329   if length deps <= max_dependencies then SOME deps else NONE
   329 
   330 
   330 fun isar_dependencies_of all_facts =
   331 fun isar_dependencies_of all_names =
   331   thms_in_proof (SOME all_facts) #> trim_dependencies
   332   thms_in_proof (SOME all_names) #> trim_dependencies
   332 
   333 
   333 fun atp_dependencies_of ctxt (params as {verbose, max_facts, ...}) prover
   334 fun atp_dependencies_of ctxt (params as {verbose, max_facts, ...}) prover
   334                         auto_level facts all_names th =
   335                         auto_level facts all_names th =
   335   case isar_dependencies_of all_names th of
   336   case isar_dependencies_of all_names th of
   336     SOME [] => NONE
   337     SOME [] => NONE
   347           accum
   348           accum
   348         else case find_first (is_dep dep) facts of
   349         else case find_first (is_dep dep) facts of
   349           SOME ((name, status), th) => accum @ [((name, status), th)]
   350           SOME ((name, status), th) => accum @ [((name, status), th)]
   350         | NONE => accum (* shouldn't happen *)
   351         | NONE => accum (* shouldn't happen *)
   351       val facts =
   352       val facts =
   352         facts |> iterative_relevant_facts ctxt params prover
   353         facts |> mepo_suggested_facts ctxt params prover
   353                      (max_facts |> the_default atp_dependency_default_max_fact)
   354                      (max_facts |> the_default atp_dependency_default_max_fact)
   354                      NONE hyp_ts concl_t
   355                      NONE hyp_ts concl_t
   355               |> fold (add_isar_dep facts) (these isar_deps)
   356               |> fold (add_isar_dep facts) (these isar_deps)
   356               |> map fix_name
   357               |> map fix_name
   357     in
   358     in
   430 
   431 
   431 fun str_of_reprove (name, deps) =
   432 fun str_of_reprove (name, deps) =
   432   "p " ^ escape_meta name ^ ": " ^ escape_metas deps ^ "\n"
   433   "p " ^ escape_meta name ^ ": " ^ escape_metas deps ^ "\n"
   433 
   434 
   434 fun str_of_query (parents, feats) =
   435 fun str_of_query (parents, feats) =
   435   "? " ^ escape_metas parents ^ "; " ^ escape_metas feats
   436   "? " ^ escape_metas parents ^ "; " ^ escape_metas feats ^ "\n"
   436 
   437 
   437 fun mash_CLEAR ctxt =
   438 fun mash_CLEAR ctxt =
   438   let val path = mash_model_dir () |> Path.explode in
   439   let val path = mash_model_dir () |> Path.explode in
   439     trace_msg ctxt (K "MaSh CLEAR");
   440     trace_msg ctxt (K "MaSh CLEAR");
   440     File.fold_dir (fn file => fn _ =>
   441     File.fold_dir (fn file => fn _ =>
   485          else
   486          else
   486            (trace_msg ctxt (fn () =>
   487            (trace_msg ctxt (fn () =>
   487                 "Internal error when " ^ when ^ ":\n" ^
   488                 "Internal error when " ^ when ^ ":\n" ^
   488                 ML_Compiler.exn_message exn); def)
   489                 ML_Compiler.exn_message exn); def)
   489 
   490 
       
   491 fun graph_info G =
       
   492   string_of_int (length (Graph.keys G)) ^ " node(s), " ^
       
   493   string_of_int (fold (Integer.add o length o snd) (Graph.dest G) 0) ^
       
   494   " edge(s), " ^
       
   495   string_of_int (length (Graph.minimals G)) ^ " minimal, " ^
       
   496   string_of_int (length (Graph.maximals G)) ^ " maximal"
       
   497 
   490 type mash_state = {fact_G : unit Graph.T}
   498 type mash_state = {fact_G : unit Graph.T}
   491 
   499 
   492 val empty_state = {fact_G = Graph.empty}
   500 val empty_state = {fact_G = Graph.empty}
   493 
   501 
   494 local
   502 local
   498 fun load _ (state as (true, _)) = state
   506 fun load _ (state as (true, _)) = state
   499   | load ctxt _ =
   507   | load ctxt _ =
   500     let val path = mash_state_path () in
   508     let val path = mash_state_path () in
   501       (true,
   509       (true,
   502        case try File.read_lines path of
   510        case try File.read_lines path of
   503          SOME (version' :: fact_lines) =>
   511          SOME (version' :: node_lines) =>
   504          let
   512          let
   505            fun add_edge_to name parent =
   513            fun add_edge_to name parent =
   506              Graph.default_node (parent, ())
   514              Graph.default_node (parent, ()) #> Graph.add_edge (parent, name)
   507              #> Graph.add_edge (parent, name)
   515            fun add_node line =
   508            fun add_fact_line line =
   516              case extract_node line of
   509              case extract_query line of
       
   510                ("", _) => I (* shouldn't happen *)
   517                ("", _) => I (* shouldn't happen *)
   511              | (name, parents) =>
   518              | (name, parents) =>
   512                Graph.default_node (name, ())
   519                Graph.default_node (name, ()) #> fold (add_edge_to name) parents
   513                #> fold (add_edge_to name) parents
       
   514            val fact_G =
   520            val fact_G =
   515              try_graph ctxt "loading state" Graph.empty (fn () =>
   521              try_graph ctxt "loading state" Graph.empty (fn () =>
   516                  Graph.empty |> version' = version
   522                  Graph.empty |> version' = version ? fold add_node node_lines)
   517                                 ? fold add_fact_line fact_lines)
   523          in
   518          in {fact_G = fact_G} end
   524            trace_msg ctxt (fn () =>
       
   525                "Loaded fact graph (" ^ graph_info fact_G ^ ")");
       
   526            {fact_G = fact_G}
       
   527          end
   519        | _ => empty_state)
   528        | _ => empty_state)
   520     end
   529     end
   521 
   530 
   522 fun save {fact_G} =
   531 fun save ctxt {fact_G} =
   523   let
   532   let
   524     val path = mash_state_path ()
   533     val path = mash_state_path ()
   525     fun fact_line_for name parents =
   534     fun fact_line_for name parents =
   526       escape_meta name ^ ": " ^ escape_metas parents
   535       escape_meta name ^ ": " ^ escape_metas parents
   527     val append_fact = File.append path o suffix "\n" oo fact_line_for
   536     val append_fact = File.append path o suffix "\n" oo fact_line_for
   528     fun append_entry (name, ((), (parents, _))) () =
   537     fun append_entry (name, ((), (parents, _))) () =
   529       append_fact name (Graph.Keys.dest parents)
   538       append_fact name (Graph.Keys.dest parents)
   530   in
   539   in
   531     File.write path (version ^ "\n");
   540     File.write path (version ^ "\n");
   532     Graph.fold append_entry fact_G ()
   541     Graph.fold append_entry fact_G ();
       
   542     trace_msg ctxt (fn () => "Saved fact graph (" ^ graph_info fact_G ^ ")")
   533   end
   543   end
   534 
   544 
   535 val global_state =
   545 val global_state =
   536   Synchronized.var "Sledgehammer_MaSh.global_state" (false, empty_state)
   546   Synchronized.var "Sledgehammer_MaSh.global_state" (false, empty_state)
   537 
   547 
   538 in
   548 in
   539 
   549 
   540 fun mash_map ctxt f =
   550 fun mash_map ctxt f =
   541   Synchronized.change global_state (load ctxt ##> (f #> tap save))
   551   Synchronized.change global_state (load ctxt ##> (f #> tap (save ctxt)))
   542 
   552 
   543 fun mash_get ctxt =
   553 fun mash_get ctxt =
   544   Synchronized.change_result global_state (load ctxt #> `snd)
   554   Synchronized.change_result global_state (load ctxt #> `snd)
   545 
   555 
   546 fun mash_unlearn ctxt =
   556 fun mash_unlearn ctxt =
   565         NONE => map snd maxs
   575         NONE => map snd maxs
   566       | SOME (name, names) =>
   576       | SOME (name, names) =>
   567         if Symtab.defined tab name then
   577         if Symtab.defined tab name then
   568           let
   578           let
   569             val new = (Graph.all_preds fact_G [name], name)
   579             val new = (Graph.all_preds fact_G [name], name)
   570             fun not_ancestor (_, x) (yp, _) = not (member (op =) yp x)
   580             fun is_ancestor (_, x) (yp, _) = member (op =) yp x
   571             val maxs = maxs |> filter (fn max => not_ancestor max new)
   581             val maxs = maxs |> filter (fn max => not (is_ancestor max new))
   572             val maxs = maxs |> forall (not_ancestor new) maxs ? cons new
   582             val maxs =
       
   583               if exists (is_ancestor new) maxs then maxs
       
   584               else new :: filter_out (fn max => is_ancestor max new) maxs
   573           in find_maxes (name :: seen) maxs names end
   585           in find_maxes (name :: seen) maxs names end
   574         else
   586         else
   575           find_maxes (name :: seen) maxs
   587           find_maxes (name :: seen) maxs
   576                      (Graph.Keys.fold (enqueue_new seen)
   588                      (Graph.Keys.fold (enqueue_new seen)
   577                                       (Graph.imm_preds fact_G name) names)
   589                                       (Graph.imm_preds fact_G name) names)
   583 fun max_suggs_of max_facts = max_facts + Int.min (200, max_facts)
   595 fun max_suggs_of max_facts = max_facts + Int.min (200, max_facts)
   584 
   596 
   585 fun is_fact_in_graph fact_G (_, th) =
   597 fun is_fact_in_graph fact_G (_, th) =
   586   can (Graph.get_node fact_G) (nickname_of th)
   598   can (Graph.get_node fact_G) (nickname_of th)
   587 
   599 
   588 fun mash_suggest_facts ctxt ({overlord, ...} : params) prover max_facts hyp_ts
   600 fun mash_suggested_facts ctxt ({overlord, ...} : params) prover max_facts hyp_ts
   589                        concl_t facts =
   601                          concl_t facts =
   590   let
   602   let
   591     val thy = Proof_Context.theory_of ctxt
   603     val thy = Proof_Context.theory_of ctxt
   592     val fact_G = #fact_G (mash_get ctxt)
   604     val fact_G = #fact_G (mash_get ctxt)
   593     val parents = max_facts_in_graph fact_G facts
   605     val parents = max_facts_in_graph fact_G facts
   594     val feats = features_of ctxt prover thy (Local, General) (concl_t :: hyp_ts)
   606     val feats = features_of ctxt prover thy (Local, General) (concl_t :: hyp_ts)
   754         val n =
   766         val n =
   755           if null old_facts then
   767           if null old_facts then
   756             n
   768             n
   757           else
   769           else
   758             let
   770             let
   759               fun score_of (_, th) =
   771               fun priority_of (_, th) =
   760                 random_range 0 (1000 * max_dependencies)
   772                 random_range 0 (1000 * max_dependencies)
   761                 - 500 * (th |> isar_dependencies_of all_names
   773                 - 500 * (th |> isar_dependencies_of all_names
   762                             |> Option.map length
   774                             |> Option.map length
   763                             |> the_default max_dependencies)
   775                             |> the_default max_dependencies)
   764               val old_facts =
   776               val old_facts =
   765                 old_facts |> map (`score_of)
   777                 old_facts |> map (`priority_of)
   766                           |> sort (int_ord o pairself fst)
   778                           |> sort (int_ord o pairself fst)
   767                           |> map snd
   779                           |> map snd
   768               val (reps, (n, _, _)) =
   780               val (reps, (n, _, _)) =
   769                 ([], (n, next_commit_time (), false))
   781                 ([], (n, next_commit_time (), false))
   770                 |> fold relearn_old_fact old_facts
   782                 |> fold relearn_old_fact old_facts
   848       val add_ths = Attrib.eval_thms ctxt add
   860       val add_ths = Attrib.eval_thms ctxt add
   849       fun prepend_facts ths accepts =
   861       fun prepend_facts ths accepts =
   850         ((facts |> filter (member Thm.eq_thm_prop ths o snd)) @
   862         ((facts |> filter (member Thm.eq_thm_prop ths o snd)) @
   851          (accepts |> filter_out (member Thm.eq_thm_prop ths o snd)))
   863          (accepts |> filter_out (member Thm.eq_thm_prop ths o snd)))
   852         |> take max_facts
   864         |> take max_facts
   853       fun iter () =
   865       fun mepo () =
   854         iterative_relevant_facts ctxt params prover max_facts NONE hyp_ts
   866         facts |> mepo_suggested_facts ctxt params prover max_facts NONE hyp_ts
   855                                  concl_t facts
   867                                       concl_t
       
   868               |> weight_mepo_facts
   856       fun mash () =
   869       fun mash () =
   857         mash_suggest_facts ctxt params prover max_facts hyp_ts concl_t facts
   870         mash_suggested_facts ctxt params prover max_facts hyp_ts concl_t facts
   858       val mess =
   871       val mess =
   859         [] |> (if fact_filter <> mashN then cons (iter (), []) else I)
   872         [] |> (if fact_filter <> mashN then cons (mepo (), []) else I)
   860            |> (if fact_filter <> mepoN then cons (mash ()) else I)
   873            |> (if fact_filter <> mepoN then cons (mash ()) else I)
   861     in
   874     in
   862       mesh_facts max_facts mess
   875       mesh_facts max_facts mess
   863       |> not (null add_ths) ? prepend_facts add_ths
   876       |> not (null add_ths) ? prepend_facts add_ths
   864     end
   877     end