Zulip Chat Archive
Stream: new members
Topic: equality of types
Greg Price (Mar 29 2021 at 05:21):
I have the feeling that I don't fully understand yet the boundaries of how I can use the fact that two types are equal. Here's a MWE:
universe u
def X : Type u := sorry
def q : X → Prop := sorry
def p : Prop := sorry
def W : Type u := X
lemma W_eq_X : W = X := rfl
lemma exists_X_eq_p : (∃ (x : X), q x) = p := sorry
example : (∃ (x : W), q x) = p :=
begin
-- This fails:
/-
rewrite tactic failed, motive is not type correct
λ (_a : Type ?), (∃ (x : W), q x) = p = ((∃ (x : _a), q x) = p)
state:
⊢ (∃ (x : W), q x) = p
-/
rw W_eq_X,
exact exists_X_eq_p,
end
example : (∃ (x : W), q x) = p :=
begin
-- But this works, and does basically the same thing:
transitivity (∃ (x : X), q x), refl,
exact exists_X_eq_p,
end
Greg Price (Mar 29 2021 at 05:24):
Here the types W and X are the same. But if I try to use that equality with rw
/ rewrite
, I get an error.
But then on the other hand I can accomplish exactly the same thing -- that is, make exactly the same change to what the goal is, effectively replacing W with X -- by using transitivity
with an appropriate argument, and then refl
.
Greg Price (Mar 29 2021 at 05:30):
One piece of this which I definitely don't understand is the error message: "motive is not type correct". I've seen "motive" mentioned in the documentation, in the context of explaining rec
/ rec_on
for inductive types. So one specific question is: is this "motive" from a rec
call that rewrite
is using internally?
I don't really have a firm grasp of what it means there either, though, and would be grateful for any explanation of that.
Then: which piece of this is the "motive" that is not type correct? Is it that whole equality on the next line of the message? And what is the _a : Type ?
in that equality?
Greg Price (Mar 29 2021 at 05:36):
Stepping back from the specifics of the error message, I think I have an idea -- thanks to the process of producing this MWE -- of what may be the reason why the rw W_eq_X
isn't accepted while the transitivity …, refl
is.
I think perhaps the point is something to do with Lean wanting to verify that the types are not just equal, but definitionally equal, before accepting one as a substitute for another when it's being used in the type of some other variable. So the transitivity
just introduces a new goal, which is fine, an equality between two expressions of type Prop
, which is fine. And then refl
asks Lean to look and see that in fact the types tucked inside, along with everything else, are definitionally equal and to discharge that goal.
Greg Price (Mar 29 2021 at 05:37):
But in rw W_eq_X
, I'm asking Lean to use the fact that those types are merely equal, as previously proven, and merely-equal isn't good enough.
Greg Price (Mar 29 2021 at 05:39):
It happens that indeed that equality is a definitional equality -- in fact it was proven in the first place with just rfl
. But the fact which I'm giving rw
to try to use is the fact of mere equality.
Greg Price (Mar 29 2021 at 05:49):
If that's right, then my practical question is: let's imagine that in a real development, W is some definition and X is the details of its construction, and I want to be able to refer to the thing by its name, W, in the statements of theorems, but then in their proofs I want to unpack it into its implementation X in order to get at the details.
I'd like to take the fact that W equals X, and package it up into some simp lemmas: maybe one for W = X
itself, or maybe several for things like projections out of it. Or even non-simp lemmas -- but just lemmas that I can refer to by name, so I can pass them to rw
or simp
to specify what to do with some expression involving W
.
And that works great, except that with the tools I've learned how to use so far, this doesn't seem to be possible where W
is being used in a type. What are ways that people handle this?
Yury G. Kudryashov (Mar 29 2021 at 06:07):
If W = X
is a rfl
, then dsimp
should be able to apply it anywhere, incl. inside types.
Yury G. Kudryashov (Mar 29 2021 at 06:08):
(though we don't use this style in mathlib, so I'm not completely sure that it'll work in all cases)
Greg Price (Mar 29 2021 at 07:05):
Yury G. Kudryashov said:
If
W = X
is arfl
, thendsimp
should be able to apply it anywhere, incl. inside types.
Oh very interesting, thanks!
Greg Price (Mar 29 2021 at 07:07):
If this style isn't used in mathlib, what are the alternative idioms that are used instead?
Or perhaps the answer is that it depends a lot on the details -- in that case I can try to expand the example to be concrete enough for an answer.
Greg Price (Mar 29 2021 at 07:12):
Trying out dsimp
in my example, this works great:
example : (∃ (x : W), q x) = p :=
begin
dsimp only [W_eq_X],
exact exists_X_eq_p,
end
Eric Wieser (Mar 29 2021 at 08:57):
I think subst W_eq_X
would work too
Mario Carneiro (Mar 29 2021 at 09:13):
dsimp
is fine; also dunfold
and delta
will do definitional rewriting
Mario Carneiro (Mar 29 2021 at 09:15):
The "motive is not type correct" error is because rewrite needs to generalize the variable in the position of W
/X
, and this variable is not defeq to either W
or X
, meaning that things that depend on it, like Exists
in this case, will fail to type check
Kevin Buzzard (Mar 29 2021 at 09:19):
This is a limitation of Lean's tactics, rather than a limitation of dependent type theory, right?
Greg Price (Mar 29 2021 at 16:40):
subst
fails but for an unrelated reason:
example : (∃ (x : W), q x) = p :=
begin
/-
subst tactic failed, given expression is not a local constant
state:
⊢ (∃ (x : W), q x) = p
-/
subst W_eq_X,
exact exists_X_eq_p,
end
Greg Price (Mar 29 2021 at 16:44):
I think it's saying that it will only work if the thing being replaced, here W
, is a local constant i.e. from the local context. I'm pretty sure I've run into this in situations where that applied, too, so that may also come in handy.
Mario Carneiro (Mar 29 2021 at 16:45):
Right, at least one side of the equality has to be a variable for subst
to apply
Greg Price (Mar 29 2021 at 16:46):
Mario Carneiro said:
dsimp
is fine; alsodunfold
anddelta
will do definitional rewriting
These also work, thanks! I had not seen those tactics before.
example : (∃ (x : W), q x) = p :=
begin
dunfold W,
exact exists_X_eq_p,
end
example : (∃ (x : W), q x) = p :=
begin
delta W,
exact exists_X_eq_p,
end
Mario Carneiro (Mar 29 2021 at 16:46):
you shouldn't use delta
under normal circumstances, it's a very low level tactic
Mario Carneiro (Mar 29 2021 at 16:47):
I'm not actually sure what the difference between dsimp
and dunfold
is; they have slightly different configuration options but both call in to the same underlying rewriting mechanism
Mario Carneiro (Mar 29 2021 at 16:48):
(the syntax is a bit different)
Greg Price (Mar 29 2021 at 16:48):
The docstring for delta
says:
Similar to
dunfold
, but performs a raw delta reduction, rather than using an equation associated with the defined constants.
The term "delta reduction" is new to me. The web tells me it means evaluating a primitive function on given arguments -- a lot like a beta reduction, it seems, but with a primitive rather than an explicit lambda expression.
Mario Carneiro (Mar 29 2021 at 16:48):
delta reduction is replacing a constant defined with def
with the body of the definition, i.e. the original meaning of "true by definition"
Mario Carneiro (Mar 29 2021 at 16:49):
this is what makes def
different from, say, constant
Greg Price (Mar 29 2021 at 16:50):
Ah interesting, that's a bit different from the definition I'd found (in a non-Lean context), then -- though clearly related
Greg Price (Mar 29 2021 at 16:51):
What is it that dunfold
does, or unfold
for that matter? The docstring for dunfold
points at unfold
, and the latter says it uses "equational lemmas associated with the definition".
Greg Price (Mar 29 2021 at 16:52):
But I'm not sure what an "equational lemma" is. Is it just a lemma which has an equality as its conclusion?
Mario Carneiro (Mar 29 2021 at 16:53):
It's a set of automatically generated lemmas created with every definition made using the equation compiler
Greg Price (Mar 29 2021 at 16:54):
Hmm, I see, and:
/-
W : Type u
W.equations._eqn_1 : W = X
-/
#print prefix W
Greg Price (Mar 29 2021 at 16:54):
So I guess that's the equational lemma there.
Mario Carneiro (Mar 29 2021 at 16:54):
def foo := 1
#print foo.equations._eqn_1
-- @[_refl_lemma]
-- theorem ALC.foo.equations._eqn_1 : foo = 1 :=
-- eq.refl foo
def bar : ℕ → ℕ
| 0 := 1
| (n+1) := 0
#print prefix bar.equations
-- bar.equations._eqn_1 : bar 0 = 1
-- bar.equations._eqn_2 : ∀ (n : ℕ), bar (n + 1) = 0
Mario Carneiro (Mar 29 2021 at 16:55):
yes, that's right
Mario Carneiro (Mar 29 2021 at 16:56):
The idea is that when "unfolding" bar
, you would much rather see those equations than the actual definition:
#print bar._main
-- def bar._main : ℕ → ℕ :=
-- λ (ᾰ : ℕ), ᾰ.cases_on (id_rhs ℕ 1) (λ (ᾰ : ℕ), id_rhs ℕ 0)
Using delta bar
will give you this
Greg Price (Mar 29 2021 at 16:59):
Hmm, yeah, I see.
Greg Price (Mar 29 2021 at 17:03):
Mario Carneiro said:
The "motive is not type correct" error is because rewrite needs to generalize the variable in the position of
W
/X
, and this variable is not defeq to eitherW
orX
, meaning that things that depend on it, likeExists
in this case, will fail to type check
Interesting. So rewrite
in order to operate needs the thing it's rewriting to be generalizable -- meaning I guess that it can be replaced with anything with the same type, and the expression as a whole would still type-check. So in particular this is unlikely to work with anything that's used in the type of something else.
Whereas dsimp
, dunfold
, delta
, and perhaps subst
but I haven't tested it, are able to use the more specific identity of what they're substituting in, and the fact that it's defeq to the thing being replaced.
Mario Carneiro (Mar 29 2021 at 17:33):
subst
isn't in the same category of the others, but change
also deserves a place on that list. These tactics all change the goal to something definitionally equal, and they do it by basically doing nothing - the next term you give will simply have the changed type, and the kernel will end up being responsible for checking that any type mismatches that arise are correct up to defeq
Mario Carneiro (Mar 29 2021 at 17:37):
subst
is kind of like rw
, except it can't fail (assuming the target equality has a variable on one side) because it essentially comes "pre-generalized": the type-correctness of the typing context itself ensures that the operation of the tactic will go without a hitch. You can think of rw (e : a = b)
as doing have h : a = b := e, generalize : a = x, subst h
, and the generalize
step is the one that can possibly fail if there are reasons why some occurrence of a
in the context has to be specifically a
and not just any term.
Greg Price (Mar 29 2021 at 17:38):
Very interesting, thanks!
Greg Price (Mar 29 2021 at 17:45):
Mario Carneiro said:
and they do it by basically doing nothing - the next term you give will simply have the changed type, and the kernel will end up being responsible for checking that any type mismatches that arise are correct up to defeq
This touches on something else I'd like to understand better: what the interface looks like between the kernel, and the machinery on top of it that we interact with when writing proofs.
Does this example translate to the following? We had a goal, which was the expected type of a proof term to stick into the hole we're currently working to fill in our proof-being-written. And then we change the goal, just meaning that we now decide on a different type we're going to use for the proof term we actually write. So then the kernel will be handed a proof term where when it gets to the subterm we're currently writing, it expects one type, but ends up judging it to have another, and it has to check that those types are defeq.
Mario Carneiro (Mar 29 2021 at 17:55):
That's right
Mario Carneiro (Mar 29 2021 at 18:00):
Actually, I lied a bit when I said the tactics do nothing; they could validly do nothing to the term under construction but in practice they instead add an identity function to the term, i.e. if the current partial proof term is (_ : T)
and you change U
then this changes the proof term to (@id U _ : T)
. The reason for this so that the precise form of the term U
is retained in the proof term, and we ensure that the kernel will actually be faced with a defeq goal of the form T = U
and not T = V
where V
is the actual type of whatever you give next. Since definitional equality in lean is not transitive, this can actually impact the type correctness of some terms, but usually it just impacts performance.
Greg Price (Mar 29 2021 at 18:01):
Ah interesting
Greg Price (Mar 29 2021 at 18:04):
Mario Carneiro said:
I'd seen a brief mention of this fact in the docs somewhere. But it looks like this is a rather more detailed discussion, thanks! I'll have to come back and read more later.
Last updated: Dec 20 2023 at 11:08 UTC