Zulip Chat Archive

Stream: general

Topic: of_as_true proofs


Joe Hendrix (Dec 28 2018 at 02:40):

I'm trying to build up a mental model of the efficiency of using decidable predicates to construct proofs in tactics..
Let's say I have a decidable predicate P : nat -> Prop with an instance h : decidable_pred P, and a concrete value x : nat in a tactic.
I can match on h x within the tactic monad (which I think is using the VM), and see if the prop is true or false.
However, I'd also like to have an expression for the proof of P x. I seem to be able to do that via an expression like @of_as_true _ (h x) trivial, but I think this results in the elaborator needing to reduce h x to whnf. I think of this as slower than the VM, since among other things nat will have a unary representation.
Is the above correct, and is so is there some way like reflection to have the VM (or the compiler in Lean 4) run h x and efficiently construct the needed proof?

Mario Carneiro (Dec 28 2018 at 04:12):

No, you can't directly produce a "fast" kernel proof from a "fast" VM function. For many common operations on nat, norm_num will produce efficient kernel proofs using VM evaluation to guide the construction, but the method is generally completely different than what the decidable instance does

Joe Hendrix (Dec 28 2018 at 06:06):

Good to know I'm not missing something. I think it would be useful to have some way to write a verified decision procedure in logic mode, and then be able to use it to discharge proof obligations in tactic mode.
For similar reasons, I started down the path of building up has_reflect instances for datatypes in proofs (e.g., has_reflect (and P Q)), but it's (1) a lot of work, and (2) still unclear how to support for some properties. Specifically, I have decidable (\forall (x : fin n), P x), but I'm struck on has_reflect (\forall (x : fin n), P x).

Mario Carneiro (Dec 28 2018 at 06:10):

hm, that's an interesting use of has_reflect. Usually we don't use has_reflect for propositions, what's the goal here?

Mario Carneiro (Dec 28 2018 at 06:11):

I think what you want is actually decidable, or at least semidecidable (where semidecidable p is basically option p)

Mario Carneiro (Dec 28 2018 at 06:11):

but you are limited by the same things as decidable proofs

Joe Hendrix (Dec 28 2018 at 06:11):

The idea for that use was to get the VM to produce the proof object using the decidable instance, then call has_reflect.reflect to construct the appropriate expression.

Mario Carneiro (Dec 28 2018 at 06:12):

decidable instances don't have proof objects

Mario Carneiro (Dec 28 2018 at 06:12):

they put all the computation in the kernel

Joe Hendrix (Dec 28 2018 at 06:13):

I'm referring to the proof object p that is the argument to decidable.is_true p

Mario Carneiro (Dec 28 2018 at 06:14):

You could infer decidable p, normalize it to is_true h and return h, but this is going to be almost as slow as kernel computing it

Mario Carneiro (Dec 28 2018 at 06:14):

you are "elaborator computing" it in this case

Joe Hendrix (Dec 28 2018 at 06:32):

I think I haven't been clear. I use the tactic decidable_tac below that uses uses the elaborator fairly often. As a work around to get more use of the VM, I was hoping to be able to use both decidable and has_reflect instances to get something a bit more efficient in the decidable_reflected tactic below:

meta def decidable_tac : tactic unit := do
  tgt ← tactic.target,
  tactic.apply ((`(@of_as_true) : expr) tgt),
  tactic.exact `(trivial)

meta def decidable_reflected (P:Prop) [h:decidable P] [g:has_reflect P] : tactic unit := do
  match h with
  | decidable.is_true p := tactic.exact (g p)
  | decidable.is_false q := tactic.fail "Prop false"
  end

Unfortunately, I can't actually write the necessary has_reflect instances. It seems like I would need some more support within Lean (perhaps constants that auto generated reflect instances for propositions).

Mario Carneiro (Dec 28 2018 at 06:43):

You can't write has_reflect instances for many propositions

Mario Carneiro (Dec 28 2018 at 06:43):

like or

Mario Carneiro (Dec 28 2018 at 06:46):

A has_reflect instance is a function that produces an expression that is a proof of p, provided p is in fact true

Mario Carneiro (Dec 28 2018 at 06:47):

So decidable p implies has_reflect p because you can evaluate the decidable instance and take the proof out

Joe Hendrix (Dec 28 2018 at 06:48):

Yep, I got and and true and thought this would work, then got stuck with forall. I tried or while writing up the example, and got the error about only being able to return a value of type P.

Mario Carneiro (Dec 28 2018 at 06:49):

If you know one or the other of the propositions is decidable, you can reflect an or

Mario Carneiro (Dec 28 2018 at 06:49):

but really decidable is the one you want, it's closed under all the operations

Joe Hendrix (Dec 28 2018 at 06:51):

Here was my attempt at or, but I can't match on the or value due to proof irrelevance:

meta instance or.reflect (P Q : Prop) [reflected P] [reflected Q] [hp:has_reflect P] [hq:reflected Q]
: has_reflect (P ∨ Q)
| (or.inl p) := `(or.inl).subst (hp p)
| (or.inr q) := `(or.inr).subst (hq q)

Mario Carneiro (Dec 28 2018 at 06:51):

exactly

Mario Carneiro (Dec 28 2018 at 06:51):

In a tactic, a proof value isn't very useful. It's only going to help avoid some code paths that weren't going to be exercised anyway

Joe Hendrix (Dec 28 2018 at 06:51):

Interesting and did not complain even though it looks like it recurses:

meta instance and.reflect (P Q : Prop) [reflected P] [reflected Q] [hp:has_reflect P] [hq:has_reflect Q]
: has_reflect (P ∧ Q)
| ⟨p,q⟩ := (`(and.intro).subst (hp p)).subst (hq q)

Mario Carneiro (Dec 28 2018 at 06:51):

and has large elimination because it only has one constructor

Joe Hendrix (Dec 28 2018 at 06:52):

The proof value is needed just so I can discharge theorems I'm proving.

Mario Carneiro (Dec 28 2018 at 06:53):

so if you have a function p -> expr coming from your has reflect instance, you could just as well have a function producing option expr with no proof preconditions

Mario Carneiro (Dec 28 2018 at 06:54):

So when you are writing a has_reflect instance you are just constructing a proof from nothing. Sometimes that's easy, like and, sometimes that requires backtracking like or, and sometimes it's really hard

Mario Carneiro (Dec 28 2018 at 06:56):

How are you supposed to use decidable_reflected?

Mario Carneiro (Dec 28 2018 at 06:56):

nothing says what P is

Mario Carneiro (Dec 28 2018 at 06:57):

it seems like it's mixing meta levels, and that's why you need has_reflect

Mario Carneiro (Dec 28 2018 at 06:57):

decidable_tac is the same as exact_dec_trivial afaict

Joe Hendrix (Dec 28 2018 at 06:58):

Let me work on an example. Is exact_dec_trivial in Lean standard library or mathlib?

Mario Carneiro (Dec 28 2018 at 06:59):

It's in mathlib, but it's literally exact dec_trivial which is core only

Joe Hendrix (Dec 28 2018 at 07:00):

I forget why I didn't just use exact dec_trivial -- I may not have known about it, I've used that tactic for a while now.

Mario Carneiro (Dec 28 2018 at 07:02):

note that dec_trivial is notation for of_as_true (by trivial); the tactic is there I think to stage the elaboration

Mario Carneiro (Dec 28 2018 at 07:06):

Okay, so I think I understand now what you are trying to do. You need a combination of decidable and has_reflect that produces exprs rather than proof values. You can automatically construct one by reflecting a decidable instance, but you can also produce much smarter proofs for nats and such

Joe Hendrix (Dec 28 2018 at 07:08):

That's right. I want proof search to run within the VM (or compiler in Lean 4), then either I can reflect on the generated proof, or maybe just have some magic constant that pretends to do it for me (say a safe version of sorry).
(edit, apparently I can't spell)

Mario Carneiro (Dec 28 2018 at 07:34):

@Joe Hendrix Here's an example of what I mean. Unfortunately I had to fight lean somewhat to write the instance, possibly the ergonomics of instance writing needs work.

@[class] meta inductive reflect_decidable (p : Prop)
| is_true (h : p) : /-reflected h-/ expr  reflect_decidable
| is_false (h : ¬ p) : /-reflected h-/ expr  reflect_decidable
open reflect_decidable

meta instance {p q : Prop} [reflected p] [reflected q] :
   [reflect_decidable p] [reflect_decidable q],
  reflect_decidable (p  q)
| (is_true h e) _ := is_true (or.inl h) (expr.app `(@or.inl p q) e)
| _ (is_true h e) := is_true (or.inr h) (expr.app `(@or.inr p q) e)
| (is_false h₁ e₁) (is_false h₂ e₂) :=
  is_false (not_or h₁ h₂) (expr.mk_app `(@not_or p q) [e₁, e₂])

meta instance : reflect_decidable true := is_true trivial `(trivial)

open tactic
meta def by_reflection : tactic unit :=
do tgt  target,
  let ty := `(reflect_decidable %%tgt),
  inst  mk_instance ty,
  is_true _ e  @tactic.eval_expr (reflect_decidable true)
    (by delta reflected; exact ty) inst,
  exact e

def ex : true  true := by by_reflection
#print ex -- or.inl trivial

Joe Hendrix (Dec 28 2018 at 09:04):

That would kind of work. One downside is that reflect_decidable is meta. I'm interested in trying to write provably correct decision procedures, not just proof producing. I'll try this approach in the short term though.

Mario Carneiro (Dec 28 2018 at 09:32):

it has to be meta, because it's creating exprs

Mario Carneiro (Dec 28 2018 at 09:33):

this is a limitation of lean 3 tactic framework

Mario Carneiro (Dec 28 2018 at 09:34):

You could create objects that are like expr but not meta, and then post process at the end, but that would add some extra overhead


Last updated: Dec 20 2023 at 11:08 UTC