feat(library/normalize): add '[unfold-f]' hint

closes #497
This commit is contained in:
Leonardo de Moura 2015-04-05 02:47:37 -07:00
parent d591c63840
commit d731a4ab13
9 changed files with 147 additions and 44 deletions

View file

@ -118,7 +118,7 @@
(,(rx symbol-start "_" symbol-end) . 'font-lock-preprocessor-face) (,(rx symbol-start "_" symbol-end) . 'font-lock-preprocessor-face)
;; modifiers ;; modifiers
(,(rx (or "\[persistent\]" "\[notation\]" "\[visible\]" "\[instance\]" "\[class\]" "\[parsing-only\]" (,(rx (or "\[persistent\]" "\[notation\]" "\[visible\]" "\[instance\]" "\[class\]" "\[parsing-only\]"
"\[coercion\]" "\[reducible\]" "\[irreducible\]" "\[semireducible\]" "\[quasireducible\]" "\[wf\]" "\[coercion\]" "\[unfold-f\]" "\[reducible\]" "\[irreducible\]" "\[semireducible\]" "\[quasireducible\]" "\[wf\]"
"\[whnf\]" "\[multiple-instances\]" "\[none\]" "\[whnf\]" "\[multiple-instances\]" "\[none\]"
"\[decls\]" "\[declarations\]" "\[all-transparent\]" "\[coercions\]" "\[classes\]" "\[decls\]" "\[declarations\]" "\[all-transparent\]" "\[coercions\]" "\[classes\]"
"\[notations\]" "\[abbreviations\]" "\[begin-end-hints\]" "\[tactic-hints\]" "\[reduce-hints\]")) "\[notations\]" "\[abbreviations\]" "\[begin-end-hints\]" "\[tactic-hints\]" "\[reduce-hints\]"))

View file

@ -300,6 +300,7 @@ struct decl_attributes {
bool m_is_class; bool m_is_class;
bool m_is_parsing_only; bool m_is_parsing_only;
bool m_has_multiple_instances; bool m_has_multiple_instances;
bool m_unfold_f_hint;
optional<unsigned> m_priority; optional<unsigned> m_priority;
optional<unsigned> m_unfold_c_hint; optional<unsigned> m_unfold_c_hint;
@ -317,6 +318,7 @@ struct decl_attributes {
m_is_class = false; m_is_class = false;
m_is_parsing_only = false; m_is_parsing_only = false;
m_has_multiple_instances = false; m_has_multiple_instances = false;
m_unfold_f_hint = false;
} }
struct elim_choice_fn : public replace_visitor { struct elim_choice_fn : public replace_visitor {
@ -421,6 +423,9 @@ struct decl_attributes {
"marked as '[parsing-only]'", pos); "marked as '[parsing-only]'", pos);
m_is_parsing_only = true; m_is_parsing_only = true;
p.next(); p.next();
} else if (p.curr_is_token(get_unfold_f_tk())) {
p.next();
m_unfold_f_hint = true;
} else if (p.curr_is_token(get_unfold_c_tk())) { } else if (p.curr_is_token(get_unfold_c_tk())) {
p.next(); p.next();
unsigned r = p.parse_small_nat(); unsigned r = p.parse_small_nat();
@ -469,7 +474,9 @@ struct decl_attributes {
if (m_is_quasireducible) if (m_is_quasireducible)
env = set_reducible(env, d, reducible_status::Quasireducible, m_persistent); env = set_reducible(env, d, reducible_status::Quasireducible, m_persistent);
if (m_unfold_c_hint) if (m_unfold_c_hint)
env = add_unfold_c_hint(env, d, m_unfold_c_hint, m_persistent); env = add_unfold_c_hint(env, d, *m_unfold_c_hint, m_persistent);
if (m_unfold_f_hint)
env = add_unfold_f_hint(env, d, m_persistent);
} }
if (m_is_class) if (m_is_class)
env = add_class(env, d, m_persistent); env = add_class(env, d, m_persistent);

View file

@ -93,7 +93,7 @@ void init_token_table(token_table & t) {
"variables", "parameter", "parameters", "constant", "constants", "[persistent]", "[visible]", "[instance]", "variables", "parameter", "parameters", "constant", "constants", "[persistent]", "[visible]", "[instance]",
"[none]", "[class]", "[coercion]", "[reducible]", "[irreducible]", "[semireducible]", "[quasireducible]", "[none]", "[class]", "[coercion]", "[reducible]", "[irreducible]", "[semireducible]", "[quasireducible]",
"[parsing-only]", "[multiple-instances]", "[parsing-only]", "[multiple-instances]",
"evaluate", "check", "eval", "[wf]", "[whnf]", "[all-transparent]", "[priority", "[unfold-c", "print", "evaluate", "check", "eval", "[wf]", "[whnf]", "[all-transparent]", "[priority", "[unfold-f]", "[unfold-c", "print",
"end", "namespace", "section", "prelude", "help", "end", "namespace", "section", "prelude", "help",
"import", "inductive", "record", "structure", "module", "universe", "universes", "local", "import", "inductive", "record", "structure", "module", "universe", "universes", "local",
"precedence", "reserve", "infixl", "infixr", "infix", "postfix", "prefix", "notation", "context", "precedence", "reserve", "infixl", "infixr", "infix", "postfix", "prefix", "notation", "context",

View file

@ -102,6 +102,7 @@ static name * g_opaque = nullptr;
static name * g_instance = nullptr; static name * g_instance = nullptr;
static name * g_priority = nullptr; static name * g_priority = nullptr;
static name * g_unfold_c = nullptr; static name * g_unfold_c = nullptr;
static name * g_unfold_f = nullptr;
static name * g_coercion = nullptr; static name * g_coercion = nullptr;
static name * g_reducible = nullptr; static name * g_reducible = nullptr;
static name * g_quasireducible = nullptr; static name * g_quasireducible = nullptr;
@ -227,6 +228,7 @@ void initialize_tokens() {
g_instance = new name("[instance]"); g_instance = new name("[instance]");
g_priority = new name("[priority"); g_priority = new name("[priority");
g_unfold_c = new name("[unfold-c"); g_unfold_c = new name("[unfold-c");
g_unfold_f = new name("[unfold-f]");
g_coercion = new name("[coercion]"); g_coercion = new name("[coercion]");
g_reducible = new name("[reducible]"); g_reducible = new name("[reducible]");
g_quasireducible = new name("[quasireducible]"); g_quasireducible = new name("[quasireducible]");
@ -290,6 +292,7 @@ void finalize_tokens() {
delete g_instance; delete g_instance;
delete g_priority; delete g_priority;
delete g_unfold_c; delete g_unfold_c;
delete g_unfold_f;
delete g_coercion; delete g_coercion;
delete g_reducible; delete g_reducible;
delete g_quasireducible; delete g_quasireducible;
@ -479,6 +482,7 @@ name const & get_opaque_tk() { return *g_opaque; }
name const & get_instance_tk() { return *g_instance; } name const & get_instance_tk() { return *g_instance; }
name const & get_priority_tk() { return *g_priority; } name const & get_priority_tk() { return *g_priority; }
name const & get_unfold_c_tk() { return *g_unfold_c; } name const & get_unfold_c_tk() { return *g_unfold_c; }
name const & get_unfold_f_tk() { return *g_unfold_f; }
name const & get_coercion_tk() { return *g_coercion; } name const & get_coercion_tk() { return *g_coercion; }
name const & get_reducible_tk() { return *g_reducible; } name const & get_reducible_tk() { return *g_reducible; }
name const & get_quasireducible_tk() { return *g_quasireducible; } name const & get_quasireducible_tk() { return *g_quasireducible; }

View file

@ -104,6 +104,7 @@ name const & get_opaque_tk();
name const & get_instance_tk(); name const & get_instance_tk();
name const & get_priority_tk(); name const & get_priority_tk();
name const & get_unfold_c_tk(); name const & get_unfold_c_tk();
name const & get_unfold_f_tk();
name const & get_coercion_tk(); name const & get_coercion_tk();
name const & get_reducible_tk(); name const & get_reducible_tk();
name const & get_semireducible_tk(); name const & get_semireducible_tk();

View file

@ -19,42 +19,64 @@ Author: Leonardo de Moura
namespace lean { namespace lean {
/** /**
\brief c_unfold hint instructs the normalizer (and simplifier) that \brief unfold hints instruct the normalizer (and simplifier) that
a function application (f a_1 ... a_i ... a_n) should be unfolded a function application. We have two kinds of hints:
when argument a_i is a constructor. - unfold_c (f a_1 ... a_i ... a_n) should be unfolded
when argument a_i is a constructor.
- unfold_f (f a_1 ... a_i ... a_n) should be unfolded when it is fully applied.
*/ */
struct unfold_c_hint_entry { struct unfold_hint_entry {
name m_decl_name; bool m_unfold_c; //!< true if it is an unfold_c hint
optional<unsigned> m_arg_idx; bool m_add; //!< add/remove hint
unfold_c_hint_entry() {} name m_decl_name;
unfold_c_hint_entry(name const & n, optional<unsigned> const & idx):m_decl_name(n), m_arg_idx(idx) {} unsigned m_arg_idx;
unfold_hint_entry():m_unfold_c(false), m_add(false), m_arg_idx(0) {}
unfold_hint_entry(bool unfold_c, bool add, name const & n, unsigned idx):
m_unfold_c(unfold_c), m_add(add), m_decl_name(n), m_arg_idx(idx) {}
}; };
static name * g_unfold_c_hint_name = nullptr; unfold_hint_entry mk_add_unfold_c_entry(name const & n, unsigned idx) { return unfold_hint_entry(true, true, n, idx); }
unfold_hint_entry mk_erase_unfold_c_entry(name const & n) { return unfold_hint_entry(true, false, n, 0); }
unfold_hint_entry mk_add_unfold_f_entry(name const & n) { return unfold_hint_entry(false, true, n, 0); }
unfold_hint_entry mk_erase_unfold_f_entry(name const & n) { return unfold_hint_entry(false, false, n, 0); }
static name * g_unfold_hint_name = nullptr;
static std::string * g_key = nullptr; static std::string * g_key = nullptr;
struct unfold_c_hint_config { struct unfold_hint_state {
typedef name_map<unsigned> state; name_map<unsigned> m_unfold_c;
typedef unfold_c_hint_entry entry; name_set m_unfold_f;
};
struct unfold_hint_config {
typedef unfold_hint_state state;
typedef unfold_hint_entry entry;
static void add_entry(environment const &, io_state const &, state & s, entry const & e) { static void add_entry(environment const &, io_state const &, state & s, entry const & e) {
if (e.m_arg_idx) if (e.m_unfold_c) {
s.insert(e.m_decl_name, *e.m_arg_idx); if (e.m_add)
else s.m_unfold_c.insert(e.m_decl_name, e.m_arg_idx);
s.erase(e.m_decl_name); else
s.m_unfold_c.erase(e.m_decl_name);
} else {
if (e.m_add)
s.m_unfold_f.insert(e.m_decl_name);
else
s.m_unfold_f.erase(e.m_decl_name);
}
} }
static name const & get_class_name() { static name const & get_class_name() {
return *g_unfold_c_hint_name; return *g_unfold_hint_name;
} }
static std::string const & get_serialization_key() { static std::string const & get_serialization_key() {
return *g_key; return *g_key;
} }
static void write_entry(serializer & s, entry const & e) { static void write_entry(serializer & s, entry const & e) {
s << e.m_decl_name << e.m_arg_idx; s << e.m_unfold_c << e.m_add << e.m_decl_name << e.m_arg_idx;
} }
static entry read_entry(deserializer & d) { static entry read_entry(deserializer & d) {
entry e; entry e;
d >> e.m_decl_name >> e.m_arg_idx; d >> e.m_unfold_c >> e.m_add >> e.m_decl_name >> e.m_arg_idx;
return e; return e;
} }
static optional<unsigned> get_fingerprint(entry const & e) { static optional<unsigned> get_fingerprint(entry const & e) {
@ -62,33 +84,53 @@ struct unfold_c_hint_config {
} }
}; };
template class scoped_ext<unfold_c_hint_config>; template class scoped_ext<unfold_hint_config>;
typedef scoped_ext<unfold_c_hint_config> unfold_c_hint_ext; typedef scoped_ext<unfold_hint_config> unfold_hint_ext;
environment add_unfold_c_hint(environment const & env, name const & n, optional<unsigned> idx, bool persistent) { environment add_unfold_c_hint(environment const & env, name const & n, unsigned idx, bool persistent) {
declaration const & d = env.get(n); declaration const & d = env.get(n);
if (!d.is_definition() || d.is_opaque()) if (!d.is_definition() || d.is_opaque())
throw exception("invalid unfold-c hint, declaration must be a non-opaque definition"); throw exception("invalid unfold-c hint, declaration must be a non-opaque definition");
return unfold_c_hint_ext::add_entry(env, get_dummy_ios(), unfold_c_hint_entry(n, idx), persistent); return unfold_hint_ext::add_entry(env, get_dummy_ios(), mk_add_unfold_c_entry(n, idx), persistent);
} }
optional<unsigned> has_unfold_c_hint(environment const & env, name const & d) { optional<unsigned> has_unfold_c_hint(environment const & env, name const & d) {
name_map<unsigned> const & s = unfold_c_hint_ext::get_state(env); unfold_hint_state const & s = unfold_hint_ext::get_state(env);
if (auto it = s.find(d)) if (auto it = s.m_unfold_c.find(d))
return optional<unsigned>(*it); return optional<unsigned>(*it);
else else
return optional<unsigned>(); return optional<unsigned>();
} }
environment erase_unfold_c_hint(environment const & env, name const & n, bool persistent) {
return unfold_hint_ext::add_entry(env, get_dummy_ios(), mk_erase_unfold_c_entry(n), persistent);
}
environment add_unfold_f_hint(environment const & env, name const & n, bool persistent) {
declaration const & d = env.get(n);
if (!d.is_definition() || d.is_opaque())
throw exception("invalid unfold-f hint, declaration must be a non-opaque definition");
return unfold_hint_ext::add_entry(env, get_dummy_ios(), mk_add_unfold_f_entry(n), persistent);
}
bool has_unfold_f_hint(environment const & env, name const & d) {
unfold_hint_state const & s = unfold_hint_ext::get_state(env);
return s.m_unfold_f.contains(d);
}
environment erase_unfold_f_hint(environment const & env, name const & n, bool persistent) {
return unfold_hint_ext::add_entry(env, get_dummy_ios(), mk_erase_unfold_f_entry(n), persistent);
}
void initialize_normalize() { void initialize_normalize() {
g_unfold_c_hint_name = new name("c-unfold"); g_unfold_hint_name = new name("unfold-hints");
g_key = new std::string("c-unfold"); g_key = new std::string("unfoldh");
unfold_c_hint_ext::initialize(); unfold_hint_ext::initialize();
} }
void finalize_normalize() { void finalize_normalize() {
unfold_c_hint_ext::finalize(); unfold_hint_ext::finalize();
delete g_unfold_c_hint_name; delete g_unfold_hint_name;
delete g_key; delete g_key;
} }
@ -113,6 +155,10 @@ class normalize_fn {
return ::lean::has_unfold_c_hint(m_tc.env(), const_name(f)); return ::lean::has_unfold_c_hint(m_tc.env(), const_name(f));
} }
bool has_unfold_f_hint(expr const & f) {
return is_constant(f) && ::lean::has_unfold_f_hint(m_tc.env(), const_name(f));
}
expr normalize_app(expr const & e) { expr normalize_app(expr const & e) {
buffer<expr> args; buffer<expr> args;
bool modified = false; bool modified = false;
@ -123,6 +169,12 @@ class normalize_fn {
modified = true; modified = true;
a = new_a; a = new_a;
} }
if (has_unfold_f_hint(f)) {
if (!is_pi(m_tc.whnf(m_tc.infer(e).first).first)) {
if (optional<expr> r = unfold_app(m_tc.env(), mk_rev_app(f, args)))
return normalize(*r);
}
}
if (auto idx = has_unfold_c_hint(f)) { if (auto idx = has_unfold_c_hint(f)) {
if (*idx < args.size() && is_constructor_app(m_tc.env(), args[args.size() - *idx - 1])) { if (*idx < args.size() && is_constructor_app(m_tc.env(), args[args.size() - *idx - 1])) {
if (optional<expr> r = unfold_app(m_tc.env(), mk_rev_app(f, args))) if (optional<expr> r = unfold_app(m_tc.env(), mk_rev_app(f, args)))

View file

@ -32,27 +32,26 @@ expr normalize(type_checker & tc, expr const & e, constraint_seq & cs, bool eta
expr normalize(type_checker & tc, expr const & e, std::function<bool(expr const&)> const & pred, // NOLINT expr normalize(type_checker & tc, expr const & e, std::function<bool(expr const&)> const & pred, // NOLINT
constraint_seq & cs, bool eta = false); constraint_seq & cs, bool eta = false);
/** \brief c_unfold hint instructs the normalizer (and simplifier) that /** \brief unfold-c hint instructs the normalizer (and simplifier) that
a function application (f a_1 ... a_i ... a_n) should be unfolded a function application (f a_1 ... a_i ... a_n) should be unfolded
when argument a_i is a constructor. when argument a_i is a constructor.
The constant will be unfolded even if it the whnf procedure did not unfolded it. The constant will be unfolded even if it the whnf procedure did not unfolded it.
Of course, kernel opaque constants are not unfolded. Of course, kernel opaque constants are not unfolded.
\remark If idx is none, then the hint is removed.
*/ */
environment add_unfold_c_hint(environment const & env, name const & n, optional<unsigned> idx, bool persistent = true); environment add_unfold_c_hint(environment const & env, name const & n, unsigned idx, bool persistent = true);
inline environment add_unfold_c_hint(environment const & env, name const & n, unsigned idx, bool persistent = true) { environment erase_unfold_c_hint(environment const & env, name const & n, bool persistent = true);
return add_unfold_c_hint(env, n, optional<unsigned>(idx), persistent);
}
inline environment erase_unfold_c_hint(environment const & env, name const & n, bool persistent = true) {
return add_unfold_c_hint(env, n, optional<unsigned>(), persistent);
}
/** \brief Retrieve the hint added with the procedure add_unfold_c_hint. */ /** \brief Retrieve the hint added with the procedure add_unfold_c_hint. */
optional<unsigned> has_unfold_c_hint(environment const & env, name const & d); optional<unsigned> has_unfold_c_hint(environment const & env, name const & d);
/** \brief unfold-f hint instructs normalizer (and simplifier) that function application
(f a_1 ... a_n) should be unfolded when it is fully applied */
environment add_unfold_f_hint(environment const & env, name const & n, bool persistent = true);
environment erase_unfold_f_hint(environment const & env, name const & n, bool persistent = true);
/** \brief Retrieve the hint added with the procedure add_unfold_f_hint. */
optional<unsigned> has_unfold_f_hint(environment const & env, name const & d);
void initialize_normalize(); void initialize_normalize();
void finalize_normalize(); void finalize_normalize();
} }

28
tests/lean/unfoldf.lean Normal file
View file

@ -0,0 +1,28 @@
open nat
definition id [unfold-f] {A : Type} (a : A) := a
definition compose {A B C : Type} (g : B → C) (f : A → B) (a : A) := g (f a)
notation g ∘ f := compose g f
example (a b : nat) (H : a = b) : id a = b :=
begin
esimp,
state,
exact H
end
example (a b : nat) (H : a = b) : (id ∘ id) a = b :=
begin
esimp,
state,
exact H
end
attribute compose [unfold-f]
example (a b : nat) (H : a = b) : (id ∘ id) a = b :=
begin
esimp,
state,
exact H
end

View file

@ -0,0 +1,12 @@
unfoldf.lean:10:2: proof state
a b : ,
H : a = b
⊢ a = b
unfoldf.lean:17:2: proof state
a b : ,
H : a = b
⊢ (id ∘ id) a = b
unfoldf.lean:26:2: proof state
a b : ,
H : a = b
⊢ a = b