Zulip Chat Archive

Stream: new members

Topic: Finding where property holds


Dan Piponi (Dec 22 2021 at 20:17):

I want to write a function that given a proof that a property holds somewhere, returns a value where it holds.

Knowing I can write

constant P :   Prop

example : ( (n : ), P n)  ( (n : ), P n) :=
  assume h,
    match h with x, y := x, y
  end

I thought I'd be able to write

def find : ( (n : ), P n)   :=
  assume h,
    match h with x, y := x
  end

But I get recursor 'Exists.dcases_on' can only eliminate into Prop.

Is there a way to implement what I want?

Stuart Presnell (Dec 22 2021 at 20:21):

Is docs#Exists.some what you're looking for?

Rob Lewis (Dec 22 2021 at 20:24):

There's also docs#nat.find . In general you can't do this computably, in which case Exists.some (=classical.some) is the right move. But in the nat case you can compute a result.

Dan Piponi (Dec 22 2021 at 20:45):

I'm working in a classical context so thanks for the pointers to Exists.some which is what I need.

What's the mechanism by which the compiler accepts my first example but not the second?

Johan Commelin (Dec 22 2021 at 20:47):

@Dan Piponi The difference is the type of your goal. In the first case you're trying to prove a Prop, so Lean knows it doesn't need to care about computational content. In the second case you're constructing data, which it refuses.

Johan Commelin (Dec 22 2021 at 20:47):

I guess that it would make sense that if you "switch on" classical mode, that the match should also work in the second case. But everybody just uses Exists.some instead.

Mauricio Collares (Dec 22 2021 at 20:53):

https://xenaproject.wordpress.com/2019/06/11/the-inverse-of-a-bijection/ is relevant here, maybe

Kyle Miller (Dec 22 2021 at 21:23):

Dan Piponi said:

What's the mechanism by which the compiler accepts my first example but not the second?

Related to what Johan said, I think about it in terms of proof irrelevance. Since all proofs of a proposition are considered to be equal, you're not allowed to use any internal details of a proof unless you're doing so in a context where those details don't matter (like when proving another proposition).

With this example you give, it takes a proof of ∃ (n : ℕ), P n and matches it against Exists.intro x y. What would it mean to return x?

def find : ( (n : ), P n)   :=
  assume h,
    match h with x, y := x
  end

Suppose Exists.intro x y and Exists.intro x' y' were two proofs of ∃ (n : ℕ), P n. By proof irrelevance, Exists.intro x y = Exists.intro x' y', and then by applying congr_arg find to this equality, the obvious interpretation would give you x = x'. This can easily be a contradiction if P is true for more than one input.

Kyle Miller (Dec 22 2021 at 21:25):

The docs#nat.find function gets around this by always returning the least natural number that satisfies P, and the docs#Exists.some function gets around this by always returning the fixed value chosen by the choice function docs#classical.choice (via docs#classical.some)

Dan Piponi (Dec 22 2021 at 21:29):

Up to now I've been ignoring the mentions of sorts and universes in the list of type rules (eg. listed here https://leanprover.github.io/reference/expressions.html) Is it a rule about universes that causes the compiler to reject my code?

Kyle Miller (Dec 22 2021 at 21:30):

So, ignoring everything about computability, there's still the underlying issue of making sure you have something that's well-defined. Proof irrelevance is sort of like a quotient, and you're required to make sure a function from a Prop is well-defined by avoiding certain things (like using match on a proof if the goal is not a Prop).

Kyle Miller (Dec 22 2021 at 21:32):

@Dan Piponi Yeah, the Prop = Sort 0 universe is special. It has proof irrelevance and propext.

Dan Piponi (Dec 22 2021 at 21:37):

Thanks Kyle. Though I'm a mathematician by training I'm trying to understand this with my programmer hat on trying to figure out the rules for writing code that doesn't produce type errors.

Kyle Miller (Dec 22 2021 at 21:44):

We can check how Exists works by seeing what Lean generates from its definition:

inductive Exists' {α : Sort u} (p : α  Prop) : Prop
| intro (w : α) (h : p w) : Exists'

set_option pp.proofs true
#print prefix Exists'
/-
Exists' : Π {α : Sort u}, (α → Prop) → Prop
Exists'.cases_on : ∀ {α : Sort u} {p : α → Prop} {motive : Prop}, Exists' p → (∀ (w : α), p w → motive) → motive
Exists'.dcases_on : ∀ {α : Sort u} {p : α → Prop} {motive : Exists' p → Prop} (n : Exists' p),
  (∀ (w : α) (h : p w), motive (Exists'.intro w h)) → motive n
Exists'.drec : ∀ {α : Sort u} {p : α → Prop} {motive : Exists' p → Prop},
  (∀ (w : α) (h : p w), motive (Exists'.intro w h)) → ∀ (n : Exists' p), motive n
Exists'.drec_on : ∀ {α : Sort u} {p : α → Prop} {motive : Exists' p → Prop} (n : Exists' p),
  (∀ (w : α) (h : p w), motive (Exists'.intro w h)) → motive n
Exists'.intro : ∀ {α : Sort u} {p : α → Prop} (w : α), p w → Exists' p
Exists'.rec : ∀ {α : Sort u} {p : α → Prop} {motive : Prop}, (∀ (w : α), p w → motive) → Exists' p → motive
Exists'.rec_on : ∀ {α : Sort u} {p : α → Prop} {motive : Prop}, Exists' p → (∀ (w : α), p w → motive) → motive
-/

The key argument that determines what you can do with an inductive type is the "motive". For example, here's the one Lean was complaining about in your example:

Exists'.dcases_on :  {α : Sort u} {p : α  Prop} {motive : Exists' p  Prop} (n : Exists' p),
  ( (w : α) (h : p w), motive (Exists'.intro w h))  motive n

The ∀ (w : α) (h : p w), motive (Exists'.intro w h) argument is what implements the idea of splitting up a proof into w and p w. Since the motive is only Prop-valued, this dcases_on is only allowed to prove some Prop given specific values that are able prove Exists' p.

Kyle Miller (Dec 22 2021 at 21:48):

Here's the non-Prop inductive type version of Exists:

inductive sigma' {α : Sort u} (p : α  Prop)
| intro (w : α) (h : p w) : sigma'

It actually remembers the w, in contrast to Exists. It's got a cases_on with a motive that can take values in universes beyond Prop:

sigma'.cases_on : Π {α : Sort u} {p : α  Prop} {motive : sigma' p  Sort l} (n : sigma' p),
  (Π (w : α) (h : p w), motive (sigma'.intro w h))  motive n

This is reflected in the fact that you are allowed to destruct the values and actually make use of them:

def find {P :   Prop} : (Σ' (n : ), P n)   :=
  assume h,
    match h with x, y := x
  end

Kyle Miller (Dec 22 2021 at 21:50):

@Dan Piponi I found these types to be rather hard to read early on, and I apologize for not really explaining them, since they are rather complicated dependent types. (I'd found it useful just knowing that this motive argument was an underlying difference, and that Lean's equation compiler relies on these generated definitions for inductive types when you write match expressions.)

Dan Piponi (Dec 22 2021 at 23:46):

Thanks @Kyle Miller! Up until you pointed out the motive thing it was looking to me like there were special rules in the language for differentiating Prop from Type but now I (think I) can see that it's the definition of Exists' that's special.

Mario Carneiro (Dec 22 2021 at 23:52):

specifically, it is the type of Exists'.rec, as compared to sigma'.rec

Mario Carneiro (Dec 22 2021 at 23:53):

And the reason for this difference is built in to lean, this is called "small elimination" of inductive types and it is specific to inductive types in Prop

Dan Piponi (Dec 23 2021 at 01:38):

@Mario Carneiro Ah. "Small elimination" is a useful term I can google . Thanks!


Last updated: Dec 20 2023 at 11:08 UTC