Zulip Chat Archive

Stream: new members

Topic: force decidability


RustyYato (Apr 16 2023 at 02:10):

Hi! Some background before my question:
This is my first stab at Lean. I have a very minimal experience with Coq in the past, but I wanted to try out alternative theorem provers to see if there were others I liked more. And Lean is a lot more user-friendly (esp around induction and recursion!)
I'm trying to create a general framework for proving numbers are prime from first principles to get a handle on how to prove things in Lean .
I know this is probably not how you would normally use Lean, but I'm new to theorem proving in general so it seems like a natural place to start.

NOTE: I've defined natural numbers like this:

inductive nat where
  | zero : nat
  | inc : nat -> nat

I've gotten to the point where I have a function to check if a number is prime or not

def nat.check_prime (n: nat) : Decidable (nat.prime n) := ...

This is implemented and it works well, the following gives the result

#eval nat.check_prime (nat.inc (nat.inc nat.zero))
isTrue _

Nice! Now I have a problem, I want to use this function to prove that 2 is prime
However, I can't figure out how to ignore to isFalse case automatically since that's impossible, and Lean seems to know that it's impossible given the eval seen above.

theorem two_is_prime : nat.prime (nat.inc (nat.inc nat.zero)) :=

How would I automatically ignore the isFalse case given this is a very concrete term

RustyYato (Apr 16 2023 at 02:11):

Here's as far as I was able to get

theorem two_is_prime : nat.prime (nat.inc (nat.inc nat.zero)) :=
  match nat.check_prime nat.zero.inc.inc with
  | Decidable.isTrue d => d
   -- no idea what to do here, and Lean doesn't let me ignore it like in other cases where a match branch is impossible
  | Decidable.isFalse d => admit

RustyYato (Apr 16 2023 at 02:13):

I was able to prove that two is prime without using this more general check_prime function, however I thought it should be possible to automatically detect if a number is prime, which led me down this rabbit hole

Mario Carneiro (Apr 16 2023 at 02:20):

RustyYato said:

I know this is probably not how you would normally use Lean, but I'm new to theorem proving in general so it seems like a natural place to start.

I take it you haven't seen #nng then. :) Reinventing the natural numbers is such a common way to start that it's now a popular pastime

Mario Carneiro (Apr 16 2023 at 02:22):

The idiomatic way to use Decidable instances to prove things is by decide:

theorem two_is_prime : nat.prime (nat.inc (nat.inc nat.zero)) := by decide

Mario Carneiro (Apr 16 2023 at 02:24):

The way it works is to use an auxiliary definition which forces the check_prime function to be evaluated for the term to typecheck

Mario Carneiro (Apr 16 2023 at 02:33):

Here's an implementation of the proof strategy from scratch:

def AsTrue (dec : Decidable p) : Prop :=
  match dec with
  | .isTrue d => True
  | .isFalse d => False

theorem of_asTrue (dec : Decidable p) : AsTrue dec  p :=
  match dec with
  | .isTrue h => fun _ => h
  | .isFalse _ => False.elim

theorem two_is_prime : nat.prime (nat.inc (nat.inc nat.zero)) :=
  of_asTrue (nat.check_prime nat.zero.inc.inc) trivial

The trick is in the trivial at the end. trivial is a proof of True, but the type that is expected is AsTrue (nat.check_prime nat.zero.inc.inc), so to make this typecheck lean will first unfold AsTrue, then see the match, then unfold nat.check_prime to get it to evaluate to isTrue of something.

Mario Carneiro (Apr 16 2023 at 02:34):

@RustyYato

RustyYato (Apr 16 2023 at 04:33):

Mario Carneiro said:

RustyYato said:

I know this is probably not how you would normally use Lean, but I'm new to theorem proving in general so it seems like a natural place to start.

I take it you haven't seen #nng then. :) Reinventing the natural numbers is such a common way to start that it's now a popular pastime

Oh wow, that's great! I'll definitely have to take a look at that.

RustyYato (Apr 16 2023 at 04:35):

Mario Carneiro said:

The idiomatic way to use Decidable instances to prove things is by decide:

theorem two_is_prime : nat.prime (nat.inc (nat.inc nat.zero)) := by decide

I see, this was the missing piece for me, thanks! And the manual implementation is super helpful, using AsTrue to force evaluation wasn't something I thought about. Seems like a nice tool for other similar situations.

Thanks a lot for the help!

RustyYato (Apr 17 2023 at 15:07):

So I tried this out, but I ran into this problem

failed to reduce to 'true'
  Decidable.rec (fun h => (fun x => false) h) (fun h => (fun x => true) h) prime_is_decidable

with this instance

instance prime_is_decidable : Decidable (nat.prime n) := nat.check_prime n

So I tried out the manual approach you showed and got

application type mismatch
  of_asTrue (nat.check_prime (nat.inc (nat.inc nat.zero))) trivial
argument
  trivial
has type
  True : Prop
but is expected to have type
  AsTrue (nat.check_prime (nat.inc (nat.inc nat.zero))) : Prop

Which looks like lean isn't able to reduce check_prime for some reason. At first I thought it was because I used well-founded recursion in by implementation of division, because of the docs for the decide tactic say that it may not work in the presence of well-founded recursion. So I went ahead and reimplemented it without well-founded recursion, and decide still doesn't work. I've tried everything I could think of and I still don't know why it's not evaluating.

Here's the entire project so far: https://github.com/RustyYato/lean-root2
Here's the definition of two is prime: https://github.com/RustyYato/lean-root2/blob/71e8179556aef1635301df3d721d72b7d89584c3/Root2/Prime/Concrete.lean#L15-L16

RustyYato (May 02 2023 at 19:12):

Wow, that took a while, but the reason why it wasn't reducing was because of how I was comparing equality of my natural numbers
After copying the implementation from the std lib, it worked.
Now to understand why

RustyYato (May 02 2023 at 19:33):

before I had equality defined like so

def nat.compare_eq (a b: nat) : Decidable (a = b) :=

  match a with
  | nat.zero => match b with
    | nat.zero => Decidable.isTrue rfl
    | nat.inc _ => Decidable.isFalse (by intro; contradiction)
  | nat.inc a₀ => match b with
    | nat.zero => Decidable.isFalse (by intro; contradiction)
    | nat.inc b₀ =>
      by rw [@nat.eq_inc_irr a₀ b₀]; exact (nat.compare_eq a₀ b₀)

where eq_inc_irr : (inc a = inc b) = (a = b). I guess that lean doesn't like wrapping and unwrapping Decidable and gives up

RustyYato (May 02 2023 at 19:38):

But I have less than and less or equal comparisons defined in a similar way, and they get evaluated just fine. So I'm not sure why this one change made the difference


Last updated: Dec 20 2023 at 11:08 UTC