123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406(* This file is free software, part of Zipperposition. See file "license" for more details. *)(** {1 Jensen-Pietrzykowski Unification} *)moduleT=TermmoduleUS=Unif_substmoduleH=HVarletprof_jp_unify=ZProf.make"jp_unify"typesubst=US.tmoduleS=structletapplyst=Subst.FO.applySubst.Renaming.none(US.substs)tletapply_tysty=Subst.Ty.applySubst.Renaming.none(US.substs)tyendletunif_simple~scopets=trylettype_unifier=Unif.FO.unify_syn~subst:Subst.empty(t,scope)(s,scope)inSome(US.of_substtype_unifier)withUnif.Fail->None(** {1 Projection rule} *)(* find substitutions for the projection rule, given a member of the disagreement pair *)letproject_onesided~scope~counter:_u=lethead,args=T.as_appuinletprefix_types,return_type=Type.open_fun(T.tyhead)inifT.is_varheadthenargs|>OSeq.of_list|>OSeq.mapi(funiarg->List.lengthprefix_types-1-i,arg)(* Determine DB-index of the argument *)|>OSeq.filter_map(fun(dbindex,arg)->(* Unify type of argument and return type *)matchunif_simple~scope(Term.of_ty(T.tyarg))(Term.of_tyreturn_type)with|Sometype_unifier->Some(dbindex,arg,type_unifier)|None->None)|>OSeq.map(fun(dbindex,arg,type_unifier)->(* substitute x for a projection to the j-th argument *)US.FO.bindtype_unifier(T.as_var_exnhead,scope)(T.fun_lprefix_types(T.bvar~ty:(T.tyarg)dbindex),scope))elseOSeq.empty(* find substitutions for the projection rule, given a disagreement pair *)letproject~scope~counteruv(_:(T.var*int)list)=OSeq.append(project_onesided~scope~counteru)(project_onesided~scope~counterv)letproject_hs_one~counterpref_typesitype_ui=letpref_types_ui,_=Type.open_funtype_uiinletn_args_free=List.lengthpref_typesinletpref_args=pref_types|>List.mapi(funity->T.bvar~ty(n_args_free-i-1))inletnew_vars=pref_types_ui|>List.map(funty->T.var(H.fresh_cnt~counter~ty:(Type.arrowpref_typesty)()))inletnew_vars_applied=new_vars|>List.map(funnv->T.appnvpref_args)inletmatrix_hd=T.bvar~ty:type_ui(n_args_free-i-1)inletmatrix=T.appmatrix_hdnew_vars_appliedinLambda.eta_expand@@T.fun_lpref_typesmatrix(** {2 Imitation rule} *)letimitate_onesided~scope~counteruv=lethead_u=T.head_term_monouinlethead_v=T.head_term_monovinletprefix_types_u,_=Type.open_fun(T.tyhead_u)inletprefix_types_v,_=Type.open_fun(T.tyhead_v)in(* assert (Type.equal ret1 ret2); *)ifT.is_varhead_u(* u has a varaible head *)&¬(T.is_bvarhead_v)&¬(T.is_funhead_v)(* the head of v is not a bound variable or a lambda-expression *)then(* create substitution: head_u |-> λ u1 ... um. head_v (x1 u1 ... um) ... (xn u1 ... um)) *)letbvars=prefix_types_u|>List.rev|>List.mapi(funity->T.bvar~tyi)|>List.revinletmatrix_args=prefix_types_v|>List.map(funprefix_type_v->letty=Type.arrowprefix_types_uprefix_type_vinletvar=T.var(H.fresh_cnt~counter~ty())inT.appvarbvars)inletmatrix=T.apphead_vmatrix_argsinletsubst_value=T.fun_lprefix_types_umatrixin(* assert (T.DB.is_closed subst_value); *)letsubst=US.FO.singleton(T.as_var_exnhead_u,scope)(subst_value,scope)inOSeq.returnsubstelseOSeq.empty(* find substitutions for the projection rule, given a disagreement pair *)letimitate~scope~counteruv(_:(T.var*int)list)=OSeq.append(imitate_onesided~scope~counteruv)(imitate_onesided~scope~countervu)(** {3 Identification rule} *)letidentify~scope~counteruv(_:(T.var*int)list)=lethead_u=T.head_term_monouinlethead_v=T.head_term_monovinletprefix_types_u,return_type=Type.open_fun(T.tyhead_u)inletprefix_types_v,_=Type.open_fun(T.tyhead_v)in(* assert (Type.equal return_type return_type2); *)ifT.is_varhead_u&&T.is_varhead_v(* TODO: necessary when args_u or args_v is empty? *)then(* create substitution: head_u |-> λ u1 ... um. x u1 ... um (y1 u1 ... um) ... (yn u1 ... um)
head_v |-> λ v1 ... vn. x (z1 v1 ... vn) ... (zm v1 ... vn) v1 ... vn *)letbvars_u=prefix_types_u|>List.rev|>List.mapi(funity->T.bvar~tyi)|>List.revinletbvars_v=prefix_types_v|>List.rev|>List.mapi(funity->T.bvar~tyi)|>List.revinletmatrix_args_u=prefix_types_v|>List.map(funprefix_type_v->letty=Type.arrowprefix_types_uprefix_type_vinletvar=T.var(H.fresh_cnt~counter~ty())inT.appvarbvars_u)inletmatrix_args_v=prefix_types_u|>List.map(funprefix_type_u->letty=Type.arrowprefix_types_vprefix_type_uinletvar=T.var(H.fresh_cnt~counter~ty())inT.appvarbvars_v)inletmatrix_head=T.var(H.fresh_cnt~counter~ty:(Type.arrow(prefix_types_u@prefix_types_v)return_type)())inletmatrix_u=T.appmatrix_head(bvars_u@matrix_args_u)inletmatrix_v=T.appmatrix_head(matrix_args_v@bvars_v)inletsubst_value_u=T.fun_lprefix_types_umatrix_uinletsubst_value_v=T.fun_lprefix_types_vmatrix_vinletsubst=US.FO.singleton(T.as_var_exnhead_u,scope)(subst_value_u,scope)inletsubst=US.FO.bindsubst(T.as_var_exnhead_v,scope)(subst_value_v,scope)inOSeq.returnsubstelseOSeq.empty(** {4 Elimination rule} *)leteliminate~scope~counter__l=l|>List.map(fun(v,k)->(* create substitution: v |-> λ u1 ... um. x u1 ... u{k-1} u{k+1} ... um *)letprefix_types,return_type=Type.open_fun(HVar.tyv)inletbvars=prefix_types|>List.rev|>List.mapi(funity->T.bvar~tyi)|>List.revinletprefix_types'=CCList.remove_at_idxkprefix_typesinletbvars'=CCList.remove_at_idxkbvarsinletmatrix_head=T.var(H.fresh_cnt~counter~ty:(Type.arrowprefix_types'return_type)())inletmatrix=T.appmatrix_headbvars'inletsubst_value=T.fun_lprefix_typesmatrixinletsubst=US.FO.singleton(v,scope)(subst_value,scope)insubst)|>OSeq.of_list(* TODO: use OSeq directly? *)(** {5 Iteration rule} *)letiterate_one~countertypes_wprefix_typesreturn_typeitype_ul=letprefix_types_ul,return_type_ul=Type.open_funtype_ulin(* create substitution: v |-> λ u1 ... um. x u1 ... um (λ w. ui (y1 (u1...um w)) ... (yn (u1...um w))) *)letinner_lambda_expr=(* create term: (λ w. ui (y1 (u1...um w)) ... (yn (u1...um w)) *)letbvars_u_under_w=prefix_types|>List.rev|>List.mapi(funity->T.bvar~ty(i+List.lengthtypes_w))|>List.revinletbvars_w=types_w|>List.rev|>List.mapi(funity->T.bvar~tyi)|>List.revinletbvar_ul_under_w=T.bvar~ty:type_ul(List.lengthprefix_types-1-i+List.lengthtypes_w)inletvars_y=prefix_types_ul|>List.map(funty->T.var(H.fresh_cnt~counter~ty:(Type.arrow(prefix_types@types_w)ty)()))inletmatrix=T.appbvar_ul_under_w(vars_y|>List.map(funy->T.appy(bvars_u_under_w@bvars_w)))inT.fun_ltypes_wmatrixinletbvars_u=prefix_types|>List.rev|>List.mapi(funity->T.bvar~tyi)|>List.revinletvar_x=T.var(H.fresh_cnt~counter~ty:(Type.arrow(prefix_types@[Type.arrowtypes_wreturn_type_ul])return_type)())inletmatrix=T.appvar_x(bvars_u@[inner_lambda_expr])inletsubst_value=T.fun_lprefix_typesmatrixinsubst_valueletiterate?(flex_same=false)~scope~counteruvl=(* The variable can be either above the disagreement pair (i.e., in l)
or it can be the head of either member of the disagreement pair *)letpositions=l|>CCList.mapfst|>CCList.cons_maybe(T.as_var(T.head_termu))|>CCList.cons_maybe(ifflex_samethenNoneelseT.as_var(T.head_termv))|>OSeq.of_list|>OSeq.flat_map(funv->letprefix_types,return_type=Type.open_fun(HVar.tyv)inprefix_types|>List.mapi(funitype_ul->(v,prefix_types,return_type,i,type_ul))|>List.fast_sort(fun(_,_,_,_,x)(_,_,_,_,y)->List.length(Type.expected_argsy)-List.length(Type.expected_argsx))|>(funl->ifnotflex_samethenlelseList.filter(fun(_,_,_,_,ty)->Type.is_funty)l)|>OSeq.of_list)in(* The tuple `w` can be of any length. Hence we use the sequence [[alpha], [alpha, beta], [alpha, beta, gamma], ...] *)lettypes_w_seq=OSeq.iterate[](funtypes_w->Type.var(H.fresh_cnt~counter~ty:Type.tType())::types_w)inifOSeq.is_emptypositionsthenOSeq.emptyelsetypes_w_seq|>OSeq.flat_map(funtypes_w->positions|>OSeq.flat_map(fun(v,prefix_types,return_type,i,type_ul)->ifType.is_funtype_ulthenOSeq.return@@Some(US.FO.singleton(v,scope)(iterate_one~countertypes_wprefix_typesreturn_typeitype_ul,scope))elseOSeq.append(ifCCList.is_emptytypes_wthenOSeq.return@@Some(US.FO.singleton(v,scope)(iterate_one~countertypes_wprefix_typesreturn_typeitype_ul,scope))elseOSeq.returnNone)(* To get a complete polymorphic algorithm, we need to consider the case that a type variable could be instantiated as a function. *)(matchType.viewtype_ulwith|Type.Varalpha->letbeta=(H.fresh_cnt~counter~ty:Type.tType())inletgamma=(H.fresh_cnt~counter~ty:Type.tType())inletalpha'=(Type.arrow[Type.varbeta](Type.vargamma))inletty_subst=US.FO.singleton(alpha,scope)(Term.of_tyalpha',scope)inletv'=HVar.cast~ty:(S.apply_tyty_subst(HVar.tyv,scope))vinletprefix_types'=prefix_types|>CCList.map(funty->S.apply_tyty_subst(ty,scope))inletreturn_type'=S.apply_tyty_subst(return_type,scope)inOSeq.return@@Some(US.FO.bindty_subst(v',scope)(iterate_one~countertypes_wprefix_types'return_type'ialpha',scope))|_->OSeq.returnNone))(* Append some "None"s to delay the substitutions containing long w tuples *)|>(funseq->OSeq.appendseq(OSeq.take50(OSeq.repeatNone))))(* TODO: use OSeq directly? *)(** {5 Unification procedure} *)(* Apply a substitution and reduce to normal form. Comparison form is actually slightly different, but eta_expand also works. *)letnfapplysu=S.applysu|>Lambda.snf|>Lambda.eta_expandletfind_disagreementst=(* TODO: preferably one that is not below a variable (to get preunification if possible) *)letrecfind_disagreement_l?(applied_var=None)?(argindex=0)sstt=matchss,ttwith|[],[]->OSeq.empty|s'::ss',t'::tt'->find_disagreement_auxs't'|>OSeq.map(fun((u,v),l)->beginmatchapplied_varwith|Somex->((u,v),(T.as_var_exnx,argindex)::l)|None->((u,v),l)end)|>(funseq->ifTerm.is_types'&¬(OSeq.is_emptyseq)then(* If type arguments need to be unified, do that first and ignore disagreements in the remaining arguments
(because the number of remaining arguments may vary in this case) *)seqelseOSeq.appendseq(find_disagreement_l~applied_var~argindex:(argindex+1)ss'tt'))|_,_->raise(Invalid_argument"types of unified terms should be equal")andfind_disagreement_auxst=ifT.is_typesthenifTerm.equalstthenOSeq.emptyelseOSeq.return((s,t),[])else(matchT.views,T.viewtwith|T.App(f,ss),T.App(g,tt)whenT.equalfg&¬(T.is_varf)->find_disagreement_lsstt|T.App(f,ss),T.App(g,tt)whenT.equalfg&&T.is_varf->find_disagreement_l~applied_var:(Somef)sstt|T.AppBuiltin(f,ss),T.AppBuiltin(g,tt)whenBuiltin.equalfg&&List.lengthss=List.lengthtt->(* ss and tt do not have to be of the same size -- AND/OR are n-ary operators *)find_disagreement_lsstt|T.Var_,T.Var_whenT.equalst->OSeq.empty|T.DBi,T.DBjwheni=j->OSeq.empty|T.Consta,T.ConstbwhenID.equalab->OSeq.empty|T.Fun(ty_s,s'),T.Fun(ty_t,t')->(* type can be different for quantifiers --
e.g. forall x:nat alpha == !! \x:nat. alpha
forall x:real beta == !! \x:real. beta *)ifnot(Type.equalty_sty_t)then(OSeq.return((s,t),[]))elsefind_disagreement_auxs't'|_->OSeq.return((s,t),[]))inlets=find_disagreement_auxstinmatchOSeq.find(fun((_,_),l)->CCList.is_emptyl)swith|Somed->Somed|None->beginmatchs()with|OSeq.Nil->None|OSeq.Cons(d,_)->Somedendletunify~scope~countert0s0=letrecunify_terms?(rules=[])ts=(* Format.printf "[UNIFYING: [(rules: %a)] [%a] and \n [%a]].\n" (CCList.pp CCString.pp) rules T.pp t T.pp s; *)matchfind_disagreementtswith|Some((u,v),l)->(* Format.printf "[Disagreement: [%a] and \n [%a]].\n" T.pp u T.pp v; *)letsubst_seq=ifT.is_typetthenOSeq.return((unif_simple~scopest),"unif_ty_args")else(ifType.equal(T.tyu)(T.tyv)then(letadd_somefuvl=f~scope~counteruvl|>OSeq.map(funs->Somes)in[add_someproject,"proj";add_someeliminate,"elim";add_someimitate,"imit";add_someidentify,"id";iterate~flex_same:false~scope~counter,"proj_hs";](* iterate must be last in this list because it is the only one with infinitely many child nodes *)|>OSeq.of_list|>OSeq.flat_map(fun(rule,rulename)->ruleuvl|>OSeq.map(funsubst->(subst,rulename))))else((* Disagreeing terms may be of different types under polymorphic builtin constants
such as equality because these builtins do not use type arguments. *)OSeq.return((unif_simple~scopest),"unif_ty")))insubst_seq|>OSeq.map(fun(osubst,rulename)->matchosubstwith|Somesubst->Util.debugf1"@[Subst (%s): @ @[%a@]@]"(funk->krulenameUS.ppsubst);Util.debugf1"@[s: %a @] \n @[t: %a @]"(funk->kT.ppsT.ppt);lett_subst=nfapplysubst(t,scope)inlets_subst=nfapplysubst(s,scope)inUtil.debugf1"@[sigma(s): %a @] \n @[sigma(t): %a @]"(funk->kT.pps_substT.ppt_subst);letunifiers=ifTerm.is_fo_termt_subst&&Term.is_fo_terms_substthenOSeq.return(unif_simple~scopet_substs_subst)elseunify_termst_substs_subst~rules:(rules@[rulename])inunifiers|>OSeq.map(CCOpt.map(fununifier->US.mergesubstunifier))(* We actually want to compose the substitutions here, but merge will have the same effect. *)|None->OSeq.empty)|>OSeq.merge|>OSeq.append(OSeq.returnNone)|None->assert(t==s);(* Util.debugf 1 "@[...unified!@ @[(rules: %a)@]@]" (fun k -> k (CCList.pp CCString.pp) rules); *)OSeq.return(SomeUS.empty)in(* Unify types first ... *)matchunif_simple~scope(T.of_ty(T.tyt0))(T.of_ty(T.tys0))with|Sometype_unifier->lett'=nfapplytype_unifier(t0,scope)inlets'=nfapplytype_unifier(s0,scope)in(* ... then terms. *)letterm_unifiers=ifTerm.is_fo_termt'&&Term.is_fo_terms'then(Util.debugf1"Doing first-order unif %a = %a"(funk->kT.ppt0T.pps0);OSeq.return(unif_simple~scopet's'))else(Util.debugf1"Doing JP unif %a = %a"(funk->kT.ppt0T.pps0);unify_termst's'~rules:[])in(* let term_unifiers = unify_terms t' s' ~rules:[] in *)OSeq.map(CCOpt.map(US.mergetype_unifier))term_unifiers|None->OSeq.empty(* TODO: Remove tracking of rules for efficiency? *)letunify_scoped(t0,scope0)(t1,scope1)=ZProf.with_profprof_jp_unify(fun()->(* Find a scope that's different from the two given ones *)letunifscope=ifscope0<scope1thenscope1+1elsescope0+1inletcounter=ref0inletadd_renamingscopesubstv=ifUS.FO.memsubst(v,scope)thensubstelseletnewvar=T.var(H.fresh_cnt~counter~ty:(S.apply_tysubst(HVar.tyv,scope))())inUS.FO.bindsubst(v,scope)(newvar,unifscope)inletsubst=US.emptyin(* Rename variables apart into scope `unifscope` *)letsubst=T.Seq.varst0|>Iter.fold(add_renamingscope0)substinletsubst=T.Seq.varst1|>Iter.fold(add_renamingscope1)substin(* Unify *)(* Util.debugf 1 "UNIFY_START %a =?= %a" (fun k -> k T.pp t0 T.pp t1); *)unify~scope:unifscope~counter(S.applysubst(t0,scope0))(S.applysubst(t1,scope1))(* merge with var renaming *)|>OSeq.map(CCOpt.map(US.mergesubst)))()letunify_scoped_nonterminatingts=OSeq.filter_map(funx->x)(unify_scopedts)(* TODO: operate on inner types like in `Unif`? Test for NO-TYPE terms? *)(* TODO: `dont care` unification, i.e. stopping at flex-flex pairs because exact result does not matter? *)