Improve higher order unification

Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
This commit is contained in:
Leonardo de Moura 2013-09-21 00:41:13 -07:00
parent 80581a76bb
commit c847d27763
5 changed files with 207 additions and 8 deletions

View file

@ -174,6 +174,8 @@ public:
void assign(expr const & m, expr const & t); void assign(expr const & m, expr const & t);
}; };
expr instantiate(expr const & s, meta_ctx const & ctx, metavar_env const & env);
/** /**
\brief Instantiate the metavariables occurring in \c e with the substitutions \brief Instantiate the metavariables occurring in \c e with the substitutions
provided by \c env. provided by \c env.

View file

@ -85,7 +85,7 @@ class ho_unifier::imp {
state_stack m_state_stack; state_stack m_state_stack;
unsigned m_delayed; unsigned m_delayed;
unsigned m_next_state_id; unsigned m_next_state_id;
bool m_ho; bool m_used_aux_vars;
volatile bool m_interrupted; volatile bool m_interrupted;
// options // options
unsigned m_max_solutions; unsigned m_max_solutions;
@ -129,7 +129,7 @@ class ho_unifier::imp {
void init(context const & ctx, expr const & l, expr const & r, metavar_env const & menv) { void init(context const & ctx, expr const & l, expr const & r, metavar_env const & menv) {
m_next_state_id = 0; m_next_state_id = 0;
m_ho = false; m_used_aux_vars = false;
m_state_stack.clear(); m_state_stack.clear();
m_state_stack.push_back(mk_state(menv, cqueue())); m_state_stack.push_back(mk_state(menv, cqueue()));
add_constraint(ctx, l, r); add_constraint(ctx, l, r);
@ -188,7 +188,7 @@ class ho_unifier::imp {
\remark \c ini_s is the initial substitution set. \c s is an extension of \c ini_s \remark \c ini_s is the initial substitution set. \c s is an extension of \c ini_s
*/ */
list<result> save_result(list<result> const & r, metavar_env s, residue const & rs, metavar_env const & ini_s) { list<result> save_result(list<result> const & r, metavar_env s, residue const & rs, metavar_env const & ini_s) {
if (empty(rs) && m_ho) { if (empty(rs) && m_used_aux_vars) {
// We only do the cleanup when we don't have a residue. // We only do the cleanup when we don't have a residue.
// If we have a residue, we can only remove auxiliary metavariables that do not occur in rs // If we have a residue, we can only remove auxiliary metavariables that do not occur in rs
s = cleanup_subst(s, ini_s); s = cleanup_subst(s, ini_s);
@ -314,6 +314,16 @@ class ho_unifier::imp {
return is_app(a) && is_metavar(arg(a, 0)); return is_app(a) && is_metavar(arg(a, 0));
} }
/** \brief Return true iff \c a is a metavariable and has a meta context. */
bool is_metavar_with_context(expr const & a) {
return is_metavar(a) && has_meta_context(a);
}
/** \brief Return true if \c a is of the form <tt>(?m[...] ...)</tt> */
bool is_meta_app_with_context(expr const & a) {
return is_meta_app(a) && has_meta_context(arg(a, 0));
}
/** /**
Auxiliary class for invoking m_type_infer. Auxiliary class for invoking m_type_infer.
If it creates a new unfication problem we mark m_failed to true. If it creates a new unfication problem we mark m_failed to true.
@ -372,8 +382,9 @@ class ho_unifier::imp {
*/ */
bool process_meta_app(context const & ctx, expr const & a, expr const & b) { bool process_meta_app(context const & ctx, expr const & a, expr const & b) {
lean_assert(is_meta_app(a)); lean_assert(is_meta_app(a));
lean_assert(!has_meta_context(arg(a, 0)));
lean_assert(!is_meta_app(b)); lean_assert(!is_meta_app(b));
m_ho = true; // mark that higher-order matching was used m_used_aux_vars = true;
expr f_a = arg(a, 0); expr f_a = arg(a, 0);
lean_assert(is_metavar(f_a)); lean_assert(is_metavar(f_a));
state top_state = m_state_stack.back(); state top_state = m_state_stack.back();
@ -438,7 +449,7 @@ class ho_unifier::imp {
// (h_2 a_1 ... a_{num_a-1} x_b) == abst_body(b) // (h_2 a_1 ... a_{num_a-1} x_b) == abst_body(b)
expr h_1 = new_s.mk_metavar(ctx); expr h_1 = new_s.mk_metavar(ctx);
expr h_2 = new_s.mk_metavar(ctx); expr h_2 = new_s.mk_metavar(ctx);
expr imitation = mk_lambda(arg_types, mk_lambda(abst_name(b), mk_app_vars(h_1, num_a - 1), mk_app_vars(h_2, num_a))); expr imitation = mk_lambda(arg_types, update_abstraction(b, mk_app_vars(h_1, num_a - 1), mk_app_vars(h_2, num_a)));
new_s.assign(midx, imitation); new_s.assign(midx, imitation);
new_q.push_front(constraint(ctx, update_app(a, 0, h_1), abst_domain(b))); new_q.push_front(constraint(ctx, update_app(a, 0, h_1), abst_domain(b)));
new_q.push_front(constraint(extend(ctx, abst_name(b), abst_domain(b)), mk_app(update_app(a, 0, h_2), Var(0)), abst_body(b))); new_q.push_front(constraint(extend(ctx, abst_name(b), abst_domain(b)), mk_app(update_app(a, 0, h_2), Var(0)), abst_body(b)));
@ -453,6 +464,73 @@ class ho_unifier::imp {
return true; return true;
} }
/** \brief Return true if \c a is of the form ?m[inst:i t, ...] */
bool is_metavar_inst(expr const & a) const {
return is_metavar(a) && has_meta_context(a) && head(metavar_ctx(a)).is_inst();
}
/**
\brief Process a constraint <tt>ctx |- a = b</tt> where \c a is of the form <tt>?m[(inst:i t), ...]</tt>.
We perform a "case split",
Case 1) ?m == #i and t == b
Case 2) imitate b
*/
void process_metavar_inst(context const & ctx, expr const & a, expr const & b) {
lean_assert(is_metavar_inst(a));
lean_assert(!is_metavar_inst(b));
lean_assert(!is_meta_app(b));
m_used_aux_vars = true;
meta_ctx mctx = metavar_ctx(a);
unsigned midx = metavar_idx(a);
unsigned i = head(mctx).s();
expr t = head(mctx).v();
state top_state = m_state_stack.back();
cqueue q = queue_of(top_state);
metavar_env s = subst_of(top_state);
m_state_stack.pop_back();
{
// Case 1
metavar_env new_s = s;
new_s.assign(midx, mk_var(i));
cqueue new_q = q;
new_q.push_front(constraint(ctx, t, b));
m_state_stack.push_back(mk_state(new_s, new_q));
}
{
// Case 2
metavar_env new_s = s;
cqueue new_q = q;
if (is_app(b)) {
// Imitation for applications b == f(s_1, ..., s_k)
// midx <- f(?h_1, ..., ?h_k)
expr f_b = arg(b, 0);
unsigned num_b = num_args(b);
buffer<expr> imitation;
imitation.push_back(f_b);
for (unsigned i = 1; i < num_b; i++)
imitation.push_back(new_s.mk_metavar(ctx));
new_s.assign(midx, mk_app(imitation.size(), imitation.data()));
} else if (is_eq(b)) {
// Imitation for equality b == Eq(s1, s2)
// midx <- Eq(?h_1, ?h_2)
expr h_1 = new_s.mk_metavar(ctx);
expr h_2 = new_s.mk_metavar(ctx);
new_s.assign(midx, mk_eq(h_1, h_2));
} else if (is_abstraction(b)) {
// Lambdas and Pis
// Imitation for Lambdas and Pis, b == Fun(x:T) B
// midx <- Fun (x:?h_1) ?h_2 x)
expr h_1 = new_s.mk_metavar(ctx);
expr h_2 = new_s.mk_metavar(ctx);
new_s.assign(midx, update_abstraction(b, h_1, mk_app(h_2, Var(0))));
} else {
new_q.push_front(constraint(ctx, pop_meta_context(a), lift_free_vars(b, i, 1)));
}
m_state_stack.push_back(mk_state(new_s, new_q));
reset_delayed();
}
}
/** /**
\brief Process the constraint \c c. Return true if the constraint was processed or postponed, and false \brief Process the constraint \c c. Return true if the constraint was processed or postponed, and false
if it failed to solve the constraint. if it failed to solve the constraint.
@ -487,8 +565,16 @@ class ho_unifier::imp {
a = head_beta_reduce(a); a = head_beta_reduce(a);
b = head_beta_reduce(b); b = head_beta_reduce(b);
} }
if ((is_metavar(a) && has_meta_context(a)) || if (is_metavar_inst(a) && (!is_metavar_inst(b) && !is_meta_app(b))) {
(is_metavar(b) && has_meta_context(b))) { process_metavar_inst(ctx, a, b);
return true;
}
if (is_metavar_inst(b) && (!is_metavar_inst(a) && !is_meta_app(a))) {
process_metavar_inst(ctx, b, a);
return true;
}
if (is_metavar_with_context(a) || is_metavar_with_context(b) ||
is_meta_app_with_context(a) || is_meta_app_with_context(b)) {
// a or b is a metavariable that has a metavariable context associated with it. // a or b is a metavariable that has a metavariable context associated with it.
// postpone // postpone
postpone_constraint(ctx, a, b); postpone_constraint(ctx, a, b);
@ -499,7 +585,7 @@ class ho_unifier::imp {
return false; return false;
switch (a.kind()) { switch (a.kind()) {
case expr_kind::Constant: case expr_kind::Var: case expr_kind::Value: case expr_kind::Type: case expr_kind::Constant: case expr_kind::Var: case expr_kind::Value: case expr_kind::Type:
return false; return a == b;
case expr_kind::Eq: case expr_kind::Eq:
add_constraint(ctx, eq_lhs(a), eq_lhs(b)); add_constraint(ctx, eq_lhs(a), eq_lhs(b));
add_constraint(ctx, eq_rhs(a), eq_rhs(b)); add_constraint(ctx, eq_rhs(a), eq_rhs(b));

View file

@ -38,6 +38,13 @@ expr update_pi(expr const & pi, expr const & d, expr const & b) {
return mk_pi(abst_name(pi), d, b); return mk_pi(abst_name(pi), d, b);
} }
expr update_abstraction(expr const & abst, expr const & d, expr const & b) {
if (is_lambda(abst))
return update_lambda(abst, d, b);
else
return update_pi(abst, d, b);
}
expr update_let(expr const & let, expr const & t, expr const & v, expr const & b) { expr update_let(expr const & let, expr const & t, expr const & v, expr const & b) {
if (is_eqp(let_type(let), t) && is_eqp(let_value(let), v) && is_eqp(let_body(let), b)) if (is_eqp(let_type(let), t) && is_eqp(let_value(let), v) && is_eqp(let_body(let), b))
return let; return let;

View file

@ -26,6 +26,10 @@ expr update_lambda(expr const & lambda, expr const & d, expr const & b);
\remark Return \c pi if the given domain and body are (pointer) equal to the ones in \c pi. \remark Return \c pi if the given domain and body are (pointer) equal to the ones in \c pi.
*/ */
expr update_pi(expr const & pi, expr const & d, expr const & b); expr update_pi(expr const & pi, expr const & d, expr const & b);
/**
\brief Return a lambda/pi expression based on \c abst with domain \c d and \c body b.
*/
expr update_abstraction(expr const & abst, expr const & d, expr const & b);
/** /**
\brief Return a let expression based on \c let with type \c t value \c v and \c body b. \brief Return a let expression based on \c let with type \c t value \c v and \c body b.

View file

@ -11,6 +11,7 @@ Author: Leonardo de Moura
#include "library/ho_unifier.h" #include "library/ho_unifier.h"
#include "library/printer.h" #include "library/printer.h"
#include "library/reduce.h" #include "library/reduce.h"
#include "library/arith/arith.h"
using namespace lean; using namespace lean;
void tst1() { void tst1() {
@ -65,8 +66,107 @@ void tst2() {
} }
} }
void tst3() {
environment env;
import_basic(env);
import_arith(env);
metavar_env menv;
ho_unifier unify(env);
expr N = Const("N");
env.add_var("N", Type());
env.add_var("f", N >> (Int >> N));
env.add_var("a", N);
env.add_var("b", N);
expr m1 = menv.mk_metavar();
expr m2 = menv.mk_metavar();
expr m3 = menv.mk_metavar();
expr t1 = menv.get_type(m1);
expr t2 = menv.get_type(m2);
expr f = Const("f");
expr a = Const("a");
expr b = Const("b");
expr l = f(m1(a), iAdd(m3, iAdd(iVal(1), iVal(1))));
expr r = f(m2(b), iAdd(iVal(1), iVal(2)));
for (auto sol : unify(context(), l, r, menv)) {
std::cout << m3 << " -> " << sol.first.get_subst(m3) << "\n";
lean_assert(sol.first.get_subst(m3) == iVal(1));
lean_assert(length(sol.second) == 1);
for (auto c : sol.second) {
std::cout << std::get<1>(c) << " == " << std::get<2>(c) << "\n";
}
}
}
void tst4() {
environment env;
metavar_env menv;
ho_unifier unify(env);
expr N = Const("N");
env.add_var("N", Type());
env.add_var("f", N >> (N >> N));
expr x = Const("x");
expr y = Const("y");
expr f = Const("f");
expr m1 = menv.mk_metavar();
expr m2 = menv.mk_metavar();
expr l = Fun({{x, N}, {y, N}}, Eq(y, f(x, m1)));
expr r = Fun({{x, N}, {y, N}}, Eq(m2, f(m1, x)));
auto sols = unify(context(), l, r, menv);
lean_assert(length(sols) == 1);
for (auto sol : sols) {
std::cout << m1 << " -> " << sol.first.get_subst(m1) << "\n";
std::cout << m2 << " -> " << sol.first.get_subst(m2) << "\n";
lean_assert(empty(sol.second));
lean_assert(beta_reduce(instantiate_metavars(l, sol.first)) ==
beta_reduce(instantiate_metavars(r, sol.first)));
}
}
void tst5() {
environment env;
metavar_env menv;
ho_unifier unify(env);
expr N = Const("N");
env.add_var("N", Type());
env.add_var("f", N >> (N >> N));
expr f = Const("f");
expr m1 = menv.mk_metavar();
expr l = f(f(m1));
expr r = f(m1);
auto sols = unify(context(), l, r, menv);
lean_assert(length(sols) == 0);
}
void tst6() {
environment env;
metavar_env menv;
ho_unifier unify(env);
expr N = Const("N");
env.add_var("N", Type());
env.add_var("f", N >> (N >> N));
expr x = Const("x");
expr y = Const("y");
expr f = Const("f");
expr m1 = menv.mk_metavar();
expr l = Fun({x, N}, Fun({y, N}, f(m1, y))(x));
expr r = Fun({x, N}, f(x, x));
auto sols = unify(context(), l, r, menv);
lean_assert(length(sols) == 2);
for (auto sol : sols) {
std::cout << m1 << " -> " << sol.first.get_subst(m1) << "\n";
std::cout << instantiate_metavars(l, sol.first) << "\n";
lean_assert(empty(sol.second));
lean_assert(beta_reduce(instantiate_metavars(l, sol.first)) ==
beta_reduce(instantiate_metavars(r, sol.first)));
}
}
int main() { int main() {
tst1(); tst1();
tst2(); tst2();
tst3();
tst4();
tst5();
tst6();
return has_violations() ? 1 : 0; return has_violations() ? 1 : 0;
} }