Zulip Chat Archive
Stream: lean4
Topic: Type ascription ignored by typeclass synthesis
Alex Keizer (Jun 23 2023 at 09:26):
I am in a situation where I want to index a Vector \a n
with a Fin (n.succ - 1)
. I am also experimenting with the following, stricter GetElem
instance, which requires indices to match.
/-
Replace the exisiting instance for indexing a collection with `Fin n`.
The new instance is more strict, only allowing a `Vector _ n` to be indexed by `Fin n`, where the
older version allowed the same vector to be indexed by, say `Fin (n+1)` if it could be proven that
the actual value of the `Fin` was less than `n`.
This extra proof causes problems in rewrites.
-/
attribute [-instance] instGetElemFinVal
section GetElem
variable (cont : ℕ → Type u)
instance [GetElem (cont n) Nat elem (fun _ i => i < n)] : GetElem (cont n) (Fin n) elem fun _ _ => True where
getElem xs i _ := getElem xs i.1 i.2
end GetElem
We have that n.succ - 1
is defEq to n
so this should work ,and I can confirm this with
variable (i : Fin (n+1-1))
#check (i : Fin n)
However v[i]
, and surprisingly also v[(i : Fin n)]
will try to find an instance for Fin (n+1-1)
and fail.
I can make it work by desugaring into
getElem (idx := Fin <| n + 1 - 1) (tail v) i True.intro
But that is not very satisfying. Note that this issue doesn't seem to be specific to the notation: using getElem
with a type ascription (and without explicitly passing idx
) also fails
getElem (tail v) (i : Fin (n.succ - 1)) True.intro -- fails
Sebastian Ullrich (Jun 23 2023 at 09:30):
A type ascription merely checks that the given type is defeq to the inferred type, it does not change the core term and thus type. You can use show t from v
or @id t v
to affect the type
Alex Keizer (Jun 23 2023 at 09:36):
That is surprising, I though it does affect type inference for things like anonymous constructors, right?
Alex Keizer (Jun 23 2023 at 09:37):
Thanks for the suggestion. This is for a simp-lemma, though, so I think I shouldn't introduce an extra id
in there
Sebastian Ullrich (Jun 23 2023 at 09:53):
It affects the expected type, but not the inferred type. That is, it only has an effect inside the parentheses, not outside
Alex Keizer (Jun 23 2023 at 09:58):
Right, that makes more sense. I got the idea from (0 : Nat)
and (0 : Int)
influencing the OfNat
instance, but that is indeed inside the parenthesis. Thanks for clarifying!
James Gallicchio (Jun 24 2023 at 22:21):
what was the reason for it being different from show t from v
? I've had to explain this before to others, and I'm never really sure what to say, since my intuition from other languages is that it should behave as show t from v
.
Kyle Miller (Jun 24 2023 at 22:31):
show t from v
is more or less let this : t := v; this
, so the type of v
is changed since there's a let binding (IIRC, the actual expansion uses let_fun
).
(x : t)
elaborates x
with t
as the expected type and then ensures the result has type t
(so, defeq check, and if it fails synthesize a coercion if possible). To get the type to actually change you'd have to insert something like @id t x
like Sebastian mentioned, which changes the term. I think you wouldn't want type ascriptions to insert lets/let_funs/ids just because the type is only defeq and not syntactically equal.
James Gallicchio (Jun 24 2023 at 22:40):
Sure, the term needs to be changed -- but from a programming background I don't have an expectation that (x : t)
leaves the term untouched (see C/Java casting). I do have an expectation that (x : t)
has type t
(syntactically). I don't think I've encountered a situation where I need the (x : t)
behavior instead of the show t from x
behavior, so that is what I'm wondering about.
Interestingly, I have encountered situations where I use (x :)
, but I think its behavior matches what I would intuitively expect.
Kyle Miller (Jun 24 2023 at 22:50):
I think one of the gotchas for programmers coming to Lean is that type ascriptions aren't casts, for example if n m : Nat
then (n + m : ℚ)
gives ↑n + ↑m
with coercions on each argument.
The way type ascriptions work is sort of a direct interface to the elaboration algorithm, which might be one answer for why they are the way they are.
Kyle Miller (Jun 24 2023 at 22:51):
(Another answer for why show t from v
and (v : t)
are different might be if they were the same then there'd be no need for two notations. I guess show t from v
is "use this type both inwards and outwards", but (v : t)
is "just use this type inwards".)
Mario Carneiro (Jun 24 2023 at 23:23):
Kyle Miller said:
(Another answer for why
show t from v
and(v : t)
are different might be if they were the same then there'd be no need for two notations. I guessshow t from v
is "use this type both inwards and outwards", but(v : t)
is "just use this type inwards".)
That's not a very convincing answer, though. I think the reason there are two notations is a combination of historical precedent and desires for those two styles of syntax in certain kinds of proof, and the elaboration differences are somewhat idiosyncratic
James Gallicchio (Jun 25 2023 at 00:50):
Yeah, I guess my complaint is if nobody wants the (x : t)
behavior as it is atm then maybe we should just make it insert a let_fun
like show
does?
Mario Carneiro (Jun 25 2023 at 01:03):
No, the difference is definitely important for some use cases
Mario Carneiro (Jun 25 2023 at 01:04):
Personally I would prefer all type ascription-like syntaxes act like (x : t)
, I don't want junk in my term
Mario Carneiro (Jun 25 2023 at 01:05):
there are some technical challenges with making this interact as intended wrt typeclass inference and the like though
James Gallicchio (Jun 25 2023 at 01:21):
hm, okay. Maybe when I use show _ from _
there's better workarounds.
Last updated: Dec 20 2023 at 11:08 UTC