Zulip Chat Archive
Stream: lean4
Topic: Fin without wrapping
Joseph O (Mar 10 2022 at 20:21):
In this photo image.png it wraps the value of 88 mod 5, but I don't really want that . I would prefer it to not typecheck, like in Idris. Is there another version of Fin
with that behavior?
Henrik Böving (Mar 10 2022 at 20:22):
You could implement one, the translation of numerals to types is controlled by the OfNat
type class
Joseph O (Mar 10 2022 at 20:24):
But why is it not default?
Eric Wieser (Mar 10 2022 at 20:51):
The simple answer is "to match the behavior of lean 3"
Henrik Böving (Mar 10 2022 at 21:04):
You could also make a mathematical argument if FIn n
is supposed to be numbers mod n
then 12 mod 10
should be 2
Joseph O (Mar 10 2022 at 21:06):
But it shouldn't
Mauricio Collares (Mar 10 2022 at 21:15):
Why? The equivalence class of 12 in the mod 10
relation exists.
Henrik Böving (Mar 10 2022 at 21:18):
You cant just say it shouldnt, this is how numbers mod n work in mathematics, just because some other language has some other opinion on what Fin
represents doesnt mean that we have to follow it
Joseph O (Mar 10 2022 at 21:28):
I wasn't contradicting how mod worked. So it seems that Fin n
represents a mod system of n
.
Damiano Testa (Mar 10 2022 at 22:03):
Also, by analogy with Lean3, an alternative possibility for fin n
would probably be absorbing addition. In particular n + n
would typecheck and be equal to n
.
Damiano Testa (Mar 10 2022 at 22:03):
I do not think that there is in Lean something that does not typechecks when you are out of bounds. The fact that functions need to be total seems an obstacle to "not typechecking sometimes".
Mario Carneiro (Mar 10 2022 at 22:06):
It is possible for lean 4 to have a type which only supports some numbers, although you have to do the computation in the typeclass system so that might not be great
Mario Carneiro (Mar 10 2022 at 22:14):
import Mathlib.Data.Nat.Basic
def Fin' (n : Nat) := Fin n
class IsPos (n : Nat) := pos : 0 < n
instance : IsPos (Nat.succ n) := ⟨Nat.succ_pos _⟩
instance [IsPos (n - i)] : OfNat (Fin' n) i :=
⟨i, Nat.lt_of_not_le fun h => ne_of_gt IsPos.pos (Nat.sub_eq_zero_iff_le.2 h)⟩
#check (3 : Fin' 5) -- ok
#check (4 : Fin' 5) -- ok
#check (5 : Fin' 5) -- fail
#check (6 : Fin' 5) -- fail
Damiano Testa (Mar 10 2022 at 22:17):
I look forward to having ℕ subtraction being undefined when it should be negative! :upside_down:
Mario Carneiro (Mar 10 2022 at 22:19):
This doesn't work as well when the numbers aren't concrete, though. For nat subtraction that would be bad because you wouldn't be able to write a - b
where a
and b
are variables
Damiano Testa (Mar 10 2022 at 22:22):
Sure, anyway, after the initial shock of being able to compute 3-4
to
- obtain a natural number,
- and moreover the natural number
0
i have grown used to it and would probably prefer the garbage values to pain involving typeclasses!
Damiano Testa (Mar 10 2022 at 22:25):
At the beginning it seemed a little a joke: "I am formalizing mathematics to not make mistakes. For instance, I can prove that 2-4=0
and that 7/0=0
".
James Gallicchio (Mar 10 2022 at 22:25):
Is there an implicit coercion from Nat to Fin n in Lean 3? I think this really only is an issue with numeric literals, since I don't think a coercion exists
Mario Carneiro (Mar 10 2022 at 22:27):
In the case of Fin'
it's probably fine since as you say you would only care if you want to write a literal, and 1 : Fin n
doesn't make sense anyway (although I can imagine that being annoying if, say, n
is a prime number)
Mario Carneiro (Mar 10 2022 at 22:28):
for nat subtraction it wouldn't really work though because the Sub
typeclass is accessed whenever you have a -
in the expression
Joseph O (Mar 11 2022 at 01:52):
Why are the bodies of the instances product types? @Mario Carneiro ?
Joseph O (Mar 11 2022 at 01:53):
Also, what does OfNat (Fin' n) i
do
Joseph O (Mar 11 2022 at 01:53):
as well as the body of the instance. Im not really sure what any of those instances do
Mario Carneiro (Mar 11 2022 at 01:54):
An instance
is a proof/construction of an element of a class
, which is a special kind of structure
, which is essentially a generalized tuple type
Mario Carneiro (Mar 11 2022 at 01:55):
In this case we are creating an inhabitant of docs4#OfNat
Mario Carneiro (Mar 11 2022 at 01:57):
Actually, now that you mention it, I'm surprised that what I wrote typechecks. Normally I would have needed to write
instance [IsPos (n - i)] : OfNat (Fin' n) i :=
⟨⟨i, Nat.lt_of_not_le fun h => ne_of_gt IsPos.pos (Nat.sub_eq_zero_iff_le.2 h)⟩⟩
which is probably easier to explain
Mario Carneiro (Mar 11 2022 at 01:58):
@Gabriel Ebner Did you know about this behavior?
example : Inhabited (Nat × Nat) := ⟨1, 2⟩
Mario Carneiro (Mar 11 2022 at 02:00):
But to get back to your question @Joseph O: The instance has the form ⟨⟨i, h⟩⟩
where the first set of brackets is destructuring the OfNat
class to produce a goal of Fin' n
, and the second set is unpacking Fin' n
which is defined to be Fin n
(docs4#Fin) which is a tuple of an element, here i
, and a proof that i < n
Joseph O (Mar 11 2022 at 02:01):
class IsPos (n : Nat) := pos : 0 < n
here, pos just returns a proof that n is greater than 0?
Joseph O (Mar 11 2022 at 02:02):
Mario Carneiro said:
But to get back to your question Joseph O: The instance has the form
⟨⟨i, h⟩⟩
where the first set of brackets is destructuring theOfNat
class to produce a goal ofFin' n
, and the second set is unpackingFin' n
which is defined to beFin n
(docs4#Fin) which is a tuple of an element, herei
, and a proof thati < n
Would i understand this all better if i read TPIL4?
Mario Carneiro (Mar 11 2022 at 02:04):
The purpose of the instance OfNat (Fin' n) i
is a signal to lean's parser. When you write 3 : Fin 5
, it expands the 3
to something like OfNat.ofNat (nat_lit 3)
where nat_lit 3
is a "raw" nat literal (these are primitive in the syntax) and ofNat
has an additional hidden argument of type OfNat (Fin' 5) (nat_lit 3)
that is inferred by typeclass inference. Since we have declared this instance
, typeclass inference will try to apply it, resulting in the subgoal IsPos (5 - 3)
. It will then try to unify this with IsPos (Nat.succ ?n)
, and lean's unifier can solve the equation 5 - 3 = Nat.succ ?n
to ?n := 1
, so the numeral typechecks
Mario Carneiro (Mar 11 2022 at 02:06):
IsPos n
is a class which is equivalent to 0 < n
(hence the name), but it is a class
which means it can be used in typeclass inference. You can't have an argument like instance [0 < n] : MyClass (foo n)
directly
Mario Carneiro (Mar 11 2022 at 02:07):
Joseph O said:
Would i understand this all better if i read TPIL4?
The answer is usually "yes" to this kind of question
Joseph O (Mar 11 2022 at 02:22):
Mario Carneiro said:
But to get back to your question Joseph O: The instance has the form
⟨⟨i, h⟩⟩
where the first set of brackets is destructuring theOfNat
class to produce a goal ofFin' n
, and the second set is unpackingFin' n
which is defined to beFin n
(docs4#Fin) which is a tuple of an element, herei
, and a proof thati < n
But why are you nesting tuples?
Mario Carneiro (Mar 11 2022 at 02:27):
Because OfNat A i
is a tuple containing A
, so OfNat (Fin n) i
is a tuple containing Fin n
which is itself a tuple containing a natural number and a proof that it is less than n
Joseph O (Mar 11 2022 at 02:32):
Why [IsPos (n - i)]
in the last instance?
Mario Carneiro (Mar 11 2022 at 02:41):
We want the typeclass problem to be solvable iff i < n
, so one way to do that without too many steps is to compute n - i
and if it is nonzero then i < n
Mario Carneiro (Mar 11 2022 at 02:44):
The set of things we can compute this way is somewhat limited, but natural number inequalities happen to be provable this way
Mario Carneiro (Mar 11 2022 at 02:45):
we probably won't be able to have a type which only admits numerals which are prime numbers
Mario Carneiro (Mar 11 2022 at 02:49):
But also, to be clear: this is totally an abuse of the typeclass and unification algorithms and I don't recommend it. There are better ways to do this with fewer downsides
Mario Carneiro (Mar 11 2022 at 02:50):
notation "♯" n => (⟨n, by decide⟩ : Fin _)
#check (♯3 : Fin 5) -- ok
#check (♯4 : Fin 5) -- ok
#check (♯5 : Fin 5) -- fail
#check (♯6 : Fin 5) -- fail
Joseph O (Mar 11 2022 at 02:51):
That is clean
Joseph O (Mar 11 2022 at 02:52):
But Im not sure how I would utilize it with functions
Mario Carneiro (Mar 11 2022 at 02:53):
This one works on primes too:
def isPrime (p : Nat) : Bool := Id.run do
for i in [2:p] do
if p % i == 0 then return false
return p ≠ 1
def Prime := {n // isPrime n}
notation "♯" n => (⟨n, by decide⟩ : Prime)
#check ♯3 -- ok
#check ♯4 -- fail
#check ♯5 -- ok
#check ♯6 -- fail
#check ♯12 -- fail
#check ♯37 -- ok
Joseph O (Mar 11 2022 at 02:55):
Mario Carneiro said:
But also, to be clear: this is totally an abuse of the typeclass and unification algorithms and I don't recommend it. There are better ways to do this with fewer downsides
I mean, I still wish I understood it. I will probably have to come back to this thread later on
Joseph O (Mar 11 2022 at 02:55):
Mario Carneiro said:
This one works on primes too:
def isPrime (p : Nat) : Bool := Id.run do for i in [2:p] do if p % i == 0 then return false return p ≠ 1 def Prime := {n // isPrime n} notation "♯" n => (⟨n, by decide⟩ : Prime) #check ♯3 -- ok #check ♯4 -- fail #check ♯5 -- ok #check ♯6 -- fail #check ♯12 -- fail #check ♯37 -- ok
Why do you need to use the do dsl?
Mario Carneiro (Mar 11 2022 at 02:56):
Here is an approach using only functions instead of notation:
def Fin.mk' (i : Nat) (h : i < n := by decide) : Fin n := ⟨i, h⟩
#check (Fin.mk' 3 : Fin 5) -- ok
#check (Fin.mk' 4 : Fin 5) -- ok
#check (Fin.mk' 5 : Fin 5) -- fail
#check (Fin.mk' 6 : Fin 5) -- fail
Joseph O (Mar 11 2022 at 02:56):
Interesting
Joseph O (Mar 11 2022 at 02:56):
what does by decide
tactic do?
Mario Carneiro (Mar 11 2022 at 02:56):
I don't need the do DSL, I use it because it is nice but you can write the same loop in other ways
Mario Carneiro (Mar 11 2022 at 02:57):
The by decide
tactic proves any decidable proposition that evaluates to true
Mario Carneiro (Mar 11 2022 at 02:58):
In this case the purpose of the isPrime
function was to give an implementation that can be executed to determine primality of a number suitable for by decide
Mario Carneiro (Mar 11 2022 at 02:58):
decidable propositions are also marked by a typeclass docs4#Decidable
Mario Carneiro (Mar 11 2022 at 02:59):
basically, a decidable proposition is one equivalent to a (computable) Bool
value
Mario Carneiro (Mar 11 2022 at 03:01):
and all the basic stuff like a < b
on natural numbers is decidable so lean knows how to evaluate it even though syntactically it has type Prop
not Bool
Joseph O (Mar 11 2022 at 03:02):
Mario Carneiro said:
notation "♯" n => (⟨n, by decide⟩ : Fin _) #check (♯3 : Fin 5) -- ok #check (♯4 : Fin 5) -- ok #check (♯5 : Fin 5) -- fail #check (♯6 : Fin 5) -- fail
So fin has an instance of Decidable?
Mario Carneiro (Mar 11 2022 at 03:02):
Prop
s in general can be undecidable, for example
def isTwinPrime (p : Nat) := isPrime p && isPrime (p + 2)
#check ∀ n, ∃ p, n < p ∧ isTwinPrime p
If you find a way to decide this proposition you will win fame and fortune
Mario Carneiro (Mar 11 2022 at 03:03):
Not Fin, the thing being decided there are propositions like 3 < 5
and 5 < 5
Mario Carneiro (Mar 11 2022 at 03:05):
If you replace by decide
with by {}
, lean will give an error showing the proposition it is trying to solve
Joseph O (Mar 11 2022 at 03:25):
Joseph O (Mar 11 2022 at 03:37):
I would like to start from the beginning. What is this typeclass instance doing, and also what really is the purpose of IsPos
,
instance : IsPos (Nat.succ n) := ⟨Nat.succ_pos _⟩
Joseph O (Mar 11 2022 at 03:37):
I feel like this would be useful for me in the future
Joseph O (Mar 11 2022 at 03:37):
No need to explain the second instance a second time.
Joseph O (Mar 11 2022 at 03:38):
But I will try to utilize the simpler solutions
Mario Carneiro (Mar 11 2022 at 04:12):
The typeclass instance makes it so that when lean wants to solve a problem of the form IsPos (Nat.succ ?n)
, it will find that proof. For example:
def foo [IsPos (Nat.succ 2)] : Nat := 0
#check foo
Writing foo
here elaborates to @foo _
where the _
is a proof of IsPos (Nat.succ 2)
, and we are asking the typeclass inference system to go find a proof using the declared instances. It will come back with myInst 2
, assuming that the name of that instance was myInst
(it has an automatically generated name, but you can name it with instance myInst : IsPos (Nat.succ n) := ⟨Nat.succ_pos _⟩
).
Mario Carneiro (Mar 11 2022 at 04:13):
The purpose of the IsPos
typeclass is to make it so that typeclass inference can solve problems of the form "is this a nonzero natural number?"
Mario Carneiro (Mar 11 2022 at 04:14):
The second instance makes use of this class to ask "is n - i
a nonzero natural number?", which is equivalent to "Is i < n
?"
Mario Carneiro (Mar 11 2022 at 04:16):
The point is that we are trying to get typeclass inference to answer this "Is i < n
?" question because this is the necessary component to make fin numerals work the way you expect: we want 3 : Fin 5
to work but not 5 : Fin 3
, and in general i : Fin n
for numerals i
and n
should work exactly when i < n
Joseph O (Mar 11 2022 at 12:29):
Ah thanks
Joseph O (Mar 11 2022 at 15:05):
how would I go about fixing this error?
failed to synthesize instance
OfNat (Fin' (length (a :: as))) 0
Kevin Buzzard (Mar 11 2022 at 15:22):
Make the instance :-) We have it in Lean 3:
instance : has_zero (fin (succ n)) := ⟨⟨0, succ_pos n⟩⟩
Joseph O (Mar 11 2022 at 18:02):
Thanks. I will just translate that to Lean 4.
Joseph O (Mar 11 2022 at 18:05):
instance : HasZero (Fin' (Nat.succ n)) :=
⟨⟨0, Nat.succ_pos n⟩⟩
it has a different name in lean 4?
Kevin Buzzard (Mar 11 2022 at 18:05):
I don't know whether the Lean 4 type class inference system will be smart enough to know that length(a::as)=succ(something)
either
Kevin Buzzard (Mar 11 2022 at 18:05):
Yeah, in Lean 4 you use this OfNat
thing instead. I don't know the details of that either.
Joseph O (Mar 11 2022 at 18:21):
well that didnt exactly work
instance : OfNat (Fin' (length (a :: as))) 0 :=
⟨⟨0, Nat.succ_pos (length (a :: as))⟩⟩
Joseph O (Mar 11 2022 at 18:42):
How would I go about making this instance?
Kevin Buzzard (Mar 11 2022 at 18:44):
I'm late to this thread and not a Lean 4 expert; can you post a mwe? I don't even know what Fin'
is.
Joseph O (Mar 11 2022 at 18:56):
Here you go
import Mathlib.Data.Nat.Basic
def Fin' (n : Nat) := Fin n
class IsPos (n : Nat) := pos : 0 < n
instance : IsPos (Nat.succ n) := ⟨Nat.succ_pos _⟩
instance [IsPos (n - i)] : OfNat (Fin' n) i :=
⟨i, Nat.lt_of_not_le fun h => ne_of_gt IsPos.pos (Nat.sub_eq_zero_iff_le.2 h)⟩
instance : OfNat (Fin' (List.length (a :: as))) 0 :=
⟨⟨0, Nat.lt 0 (List.length (a :: as))⟩⟩
Kevin Buzzard (Mar 11 2022 at 19:29):
That doesn't work because you never told Lean the type of as
.
Kevin Buzzard (Mar 11 2022 at 19:31):
and when you do, it doesn't work for the reason that the error explains: right you the second component of ⟨0, _⟩
is the statement 0 < length...
, as opposed to its proof.
Kevin Buzzard (Mar 11 2022 at 19:32):
variable (a : α) (as : List α)
instance : OfNat (Fin' (List.length (a :: as))) 0 :=
⟨⟨0, (Nat.succ_pos _ : Nat.lt 0 (List.length (a :: as)))⟩⟩
Joseph O (Mar 11 2022 at 19:41):
Ah thank you
Joseph O (Mar 11 2022 at 19:42):
Now that the instance is made, the error should go away in this function, but it isn't
def List.get'' [OfNat i 0] {α : Type u} : (as : List α) → (i : Fin' as.length) → α
| nil, i => False.elim $ Nat.not_lt_zero _ _
| cons a as, 0 => a
Joseph O (Mar 11 2022 at 19:43):
failed to synthesize instance
OfNat (Fin' (length (a :: as))) 0
Kevin Buzzard (Mar 11 2022 at 19:45):
Rotten luck -- type class inference can't find it, by the looks of things. I know nothing about how Lean 4 type class inference works I'm afraid
Gabriel Ebner (Mar 11 2022 at 19:48):
Hint: you need to write instance : OfNat (Fin' (List.length (a :: as))) (nat_lit 0)
Kevin Buzzard (Mar 11 2022 at 19:51):
[OfNat i 0]
looks like nonsense -- maybe remove it?
Joseph O (Mar 11 2022 at 20:35):
Ah yeah. That was part of my previous attempts.
Joseph O (Mar 11 2022 at 20:37):
Gabriel Ebner said:
Hint: you need to write
instance : OfNat (Fin' (List.length (a :: as))) (nat_lit 0)
Why did that fix it though?
Joseph O (Mar 11 2022 at 20:40):
Also, now the function
def List.get'' {α : Type u} : (as : List α) → (i : Fin' as.length) → α
| nil, i => False.elim $ Nat.not_lt_zero _ _
| a::as, 0 => a
| a::as, i+1 => as.get'' i
has the error
failed to synthesize instance
HAdd (Fin' (length (a :: as))) (Fin' (length (a :: as))) ?m.1019
Kevin Buzzard (Mar 11 2022 at 21:48):
You know what to do then :-)
Kevin Buzzard (Mar 11 2022 at 21:50):
You understand the error, right? You defined Fin'
so it's now your problem. If i : Fin' n
then what does i + 1
mean? No idea -- you didn't tell me what + meant, or what 1 meant.
Joseph O (Mar 12 2022 at 00:40):
I see. I think i have a fix
Joseph O (Mar 12 2022 at 00:44):
image.png Well thats strange. How do people match on Nat.succ i
then.
Joseph O (Mar 12 2022 at 00:45):
Also, for a MWE,
def Fin'.succ (n : Fin' k) := Fin.succ n
Henrik Böving (Mar 12 2022 at 11:01):
Fin.succ is not a constructor of the Fin datatype (whcih you could see by looking at its definition) thus it and any further definition cannot just be matched upon like this.
Joseph O (Mar 12 2022 at 13:36):
then, what alternative solutions lie?
Leonardo de Moura (Mar 12 2022 at 13:55):
@Joseph O Note that in other systems (e.g, Agda and Idris), Fin n
is an inductive datatype with two constructors. In Lean, it is essentially a subtype of Nat
, i.e, a pair containing: a Nat
, and a proof that it is smaller than n
. You can use match
to "access" this structure.
def get : (as : List α) → (i : Fin as.length) → α
| a::as, ⟨0, _⟩ => a
| a::as, ⟨i+1, h⟩ => get as ⟨i, Nat.lt_of_succ_lt_succ h⟩
Here is the same definition without the anonymous constructor notation.
def get : (as : List α) → (i : Fin as.length) → α
| a::as, Fin.mk 0 _ => a
| a::as, Fin.mk (i+1) h => get as (Fin.mk i (Nat.lt_of_succ_lt_succ h))
We can also define Fin n
as it is defined in other systems and then match on Fin.succ
. Example:
namespace Ex -- Using namespace `Ex` to avoid name collision with the Lean `Fin`
inductive Fin : Nat → Type
| zero : Fin (Nat.succ n)
| succ : Fin n → Fin (Nat.succ n)
deriving Repr
def get : (as : List α) → (i : Fin as.length) → α
| a::as, Fin.zero => a
| a::as, Fin.succ i => get as i
end Ex
Joseph O (Mar 12 2022 at 18:55):
Ah i see. Tho, it would still be the wrapping version I think
Joseph O (Mar 12 2022 at 18:57):
Also, why did you decide not to consider the | nil, i
case anymore?
Leonardo de Moura (Mar 12 2022 at 18:58):
Joseph O said:
Ah i see. Tho, it would still be the wrapping version I think
You can still use the wrapping if you want
def Fin' (n : Nat) := Fin n
def get : (as : List α) → (i : Fin' as.length) → α
| a::as, ⟨0, _⟩ => a
| a::as, ⟨i+1, h⟩ => get as ⟨i, Nat.lt_of_succ_lt_succ h⟩
Leonardo de Moura (Mar 12 2022 at 18:58):
Joseph O said:
Also, why did you decide not to consider the
| nil, i
case anymore?
Lean 4 can prove it is unreachable.
Joseph O (Mar 12 2022 at 18:59):
But it is reachable? Like what if someone gives List.get [] 8
Joseph O (Mar 12 2022 at 19:01):
Also, I have this error again, failed to synthesize instance
OfNat (Fin' (List.length [1, 5, 4])) 7
Leonardo de Moura (Mar 12 2022 at 19:02):
Joseph O said:
But it is reachable? Like what if someone gives
List.get [] 8
It is not. You will not be able to do that. The type Fin [].length
is not inhabited.
Henrik Böving (Mar 12 2022 at 19:02):
That would mean they are passing an element of type Fin 0
and that is equivalent to the emtpy type and thus impossible to do
Joseph O (Mar 12 2022 at 19:04):
Empty type. Ok i see.
Henrik Böving (Mar 12 2022 at 19:04):
Leonardo de Moura said:
Joseph O said:
Also, why did you decide not to consider the
| nil, i
case anymore?Lean 4 can prove it is unreachable.
This is also a thing that I've been wondering about for a bit, how does it actually automagically construct this proof? Can you explain it/point me to the implementation?
Leonardo de Moura (Mar 12 2022 at 19:04):
Joseph O said:
Also, I have this error again,
failed to synthesize instance OfNat (Fin' (List.length [1, 5, 4])) 7
I did not add OfNat
instances in my example. I just showed you how to define the get
function.
Joseph O (Mar 12 2022 at 19:04):
Right but I already have the OfNat instance
Joseph O (Mar 12 2022 at 19:05):
instance : OfNat (Fin' (List.length (a :: as))) (nat_lit 0) :=
⟨⟨0, (Nat.succ_pos _ : Nat.lt 0 (List.length (a :: as)))⟩⟩
Leonardo de Moura (Mar 12 2022 at 19:05):
Henrik Böving said:
Leonardo de Moura said:
Joseph O said:
Also, why did you decide not to consider the
| nil, i
case anymore?Lean 4 can prove it is unreachable.
This is also a thing that I've been wondering about for a bit, how does it actually automagically construct this proof? Can you explain it/point me to the implementation?
https://github.com/leanprover/lean4/blob/master/src/Lean/Meta/Match/Match.lean#L148-L152
Leonardo de Moura (Mar 12 2022 at 19:06):
Joseph O said:
instance : OfNat (Fin' (List.length (a :: as))) (nat_lit 0) := ⟨⟨0, (Nat.succ_pos _ : Nat.lt 0 (List.length (a :: as)))⟩⟩
This instance is for 0
only.
Henrik Böving (Mar 12 2022 at 19:06):
That looks quite a lot easier than I imagined :octopus:
Joseph O (Mar 12 2022 at 19:08):
Leonardo de Moura said:
Joseph O said:
instance : OfNat (Fin' (List.length (a :: as))) (nat_lit 0) := ⟨⟨0, (Nat.succ_pos _ : Nat.lt 0 (List.length (a :: as)))⟩⟩
This instance is for
0
only.
Well, does that mean I have to do it manually for every literal?
Leonardo de Moura (Mar 12 2022 at 19:21):
@Joseph O I did not read the whole thread. I think this kind of instance is not the way to go. You could add another instance for the Nat.succ
case, but I see it as an abuse of the typeclass mechanism. Moreover, you will have to do it for many other functions. These two are for List.length
. Fin
is like a subtype. So we often have to synthesize proofs. We can use tactics to synthesize the proofs. Example:
#eval get [1, 2, 3] ⟨0, by decide⟩
We can also create helper notation if we have to do it a lot.
notation "#(" a ")" => (Fin.mk a (by decide))
#eval get [1, 2, 3] #(0)
#eval get [1, 2, 3] #(1)
#eval get [1, 2, 3] #(2)
#eval get [1, 2, 3] #(3) -- Error
Joseph O (Mar 12 2022 at 23:48):
Ah cool, thanks
Joseph O (Mar 13 2022 at 01:37):
So i rewrote my nth function with this new Fin'
,
def nth {αs : List Type} : HList αs → (n : Fin' αs.length) → αs.get' n
| cons x xs, ⟨0, _⟩ => x
| cons x xs, ⟨n+1, h⟩ => xs.nth ⟨n, Nat.lt_of_succ_lt_succ h⟩
but,
when i try to run
#eval ⟪1,"2",3.0⟫.nth #(1)
, it gives this error
expression
nth (1!!"2"!!3.0!!unit) { val := 1, isLt := (_ : 1 < List.length [ℕ, String, Float]) }
has type
List.get' [ℕ, String, Float] { val := 1, isLt := (_ : 1 < List.length [ℕ, String, Float]) }
but instance
Lean.MetaEval (List.get' [ℕ, String, Float] { val := 1, isLt := (_ : 1 < List.length [ℕ, String, Float]) })
failed to be synthesized, this instance instructs Lean on how to display the resulting value, recall that any type implementing the `Repr` class also implements the `Lean.MetaEval` class
Mauricio Collares (Mar 13 2022 at 02:31):
What's the definition of List.get'
? #mwe
Mauricio Collares (Mar 13 2022 at 03:02):
(deleted)
Joseph O (Mar 13 2022 at 03:08):
def List.get' {α : Type u} : (as : List α) → (i : Fin' as.length) → α
| a::as, ⟨0, _⟩ => a
| a::as, ⟨i+1, h⟩ => as.get' ⟨i, Nat.lt_of_succ_lt_succ h⟩
```
Chris B (Mar 13 2022 at 03:14):
The error message says you need to tell Lean how to display/print the result.
Leonardo de Moura (Mar 13 2022 at 03:21):
@Joseph O During typeclass resolution, Lean only unfolds definitions tagged as [reducible]
. The motivation is to control the cost of this procedure. The result type of your example depends on List.get'
. Thus, Lean fails to build the Repr
instance for the type. Here is a working example:
inductive HList : List (Type u) → Type (u+1)
| nil : HList []
| cons : α → HList αs → HList (α::αs)
@[reducible] def List.get' : (as : List α) → (i : Fin as.length) → α
| a::as, ⟨0, _⟩ => a
| a::as, ⟨i+1, h⟩ => get' as ⟨i, Nat.lt_of_succ_lt_succ h⟩
def HList.nth : HList αs → (n : Fin αs.length) → αs.get' n
| cons x xs, ⟨0, _⟩ => x
| cons x xs, ⟨n+1, h⟩ => xs.nth ⟨n, Nat.lt_of_succ_lt_succ h⟩
notation "#(" a ")" => (Fin.mk a (by decide))
syntax "⟪" term,* "⟫" : term
macro_rules
| `(⟪ ⟫) => `(HList.nil)
| `(⟪ $a ⟫) => `(HList.cons $a HList.nil)
| `(⟪ $a, $as,* ⟫) => `(HList.cons $a ⟪$as,*⟫)
#eval ⟪10, true, 20.1⟫.nth #(0)
#eval ⟪10, true, 20.1⟫.nth #(1)
#eval ⟪10, true, 20.1⟫.nth #(2)
Leonardo de Moura (Mar 13 2022 at 03:45):
Another trick you can use is to tell Lean what type containing the List.get'
reduces too. The type checker will make sure you provided the correct type.
#eval id (α := String) (⟪1,"2",3.0⟫.nth #(1))
You can also define your own identity function where all arguments are explicit.
def the (α : Type u) (a : α) := a
#eval the String (⟪1,"2",3.0⟫.nth #(1))
Leonardo de Moura (Mar 13 2022 at 04:00):
I pushed a small improvement to the #eval
command. Now, when it fails to construct the MetaEval
instance, it reduces the result type and tries again. The next nighly build should have this improvement, and your example will work without any tricks.
Leonardo de Moura (Mar 13 2022 at 04:03):
I also added this example to the test suite: https://github.com/leanprover/lean4/blob/master/tests/lean/run/hlistOverload.lean
Joseph O (Mar 13 2022 at 12:25):
Leonardo de Moura said:
I also added this example to the test suite: https://github.com/leanprover/lean4/blob/master/tests/lean/run/hlistOverload.lean
Im honored, though how were you still able to use the brackets? I remember when I was creating the macro, it would give me an error saying that []
was already taken
Joseph O (Mar 13 2022 at 12:28):
ah, you gave it a name. Got it. Ive always wanted to use the brackets
Joseph O (Mar 13 2022 at 12:29):
Ah, its actually the kind
tag.
Joseph O (Mar 13 2022 at 12:29):
I will keep that in mind for my future macros, thanks
Joseph O (Mar 13 2022 at 15:14):
Do you mind explaining why this error? image.png
it has to do with using HList
Horatiu Cheval (Mar 13 2022 at 15:38):
Does the notation work outside of match
? For instance, in #check [1, "2"]
?
Horatiu Cheval (Mar 13 2022 at 15:39):
Might it be because there is some ToString
instance for Nat
? (just guessing)
Joseph O (Mar 13 2022 at 15:40):
Horatiu Cheval (Mar 13 2022 at 15:42):
And the same if you don't explicitely annotate the type in check
?
Joseph O (Mar 13 2022 at 15:45):
It can't decide whether Im refering to list or hlist image.png
Horatiu Cheval (Mar 13 2022 at 15:45):
Or maybe a better experiment to test the ToString
speculation, would #check ([1, "2"] : List String)
also work? (I'm not really sure that what I'm saying makes sense)
Horatiu Cheval (Mar 13 2022 at 15:46):
Oh, so it seems that's it then
Horatiu Cheval (Mar 13 2022 at 15:46):
1 can be implicitely converted to the String "1"
Joseph O (Mar 13 2022 at 15:46):
Same error image.png
Horatiu Cheval (Mar 13 2022 at 15:53):
Maybe if you write (1 : Nat)
inside the list notation? Though I'm skeptical, it is probably the same thing
Horatiu Cheval (Mar 13 2022 at 15:54):
Sorry, I don't know how to fix this, we'll have to wait for someone who knows better. At least it seems that we've figured out the root cause
Leonardo de Moura (Mar 13 2022 at 16:09):
@Joseph O You have to get the most recent nightly build. The support for overloaded notations in patterns has been implemented a few days ago.
Joseph O (Mar 13 2022 at 16:09):
Ah I see. Let me update.
Joseph O (Mar 13 2022 at 16:11):
Huh, seems like im on the latest.
Joseph O (Mar 13 2022 at 16:12):
leanprover/lean4:nightly-2022-03-09
Joseph O (Mar 13 2022 at 16:14):
I also have to use the same version as mathlib, or else I get errors.
Joseph O (Mar 13 2022 at 16:16):
`c:\Users\vatis\.elan\toolchains\leanprover--lean4---nightly\bin\lake.exe print-paths Init Mathlib.Data.Nat.Basic` failed:
stderr:
info: mathlib: trying to update .\lean_packages\mathlib to revision master
info: > LEAN_PATH=.\lean_packages\mathlib\.\build\lib;.\build\lib c:\Users\vatis\.elan\toolchains\leanprover--lean4---nightly\bin\lean.exe .\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean -R .\lean_packages\mathlib\.\. -o .\lean_packages\mathlib\.\build\lib\Mathlib\Init\Logic.olean -i .\lean_packages\mathlib\.\build\lib\Mathlib\Init\Logic.ilean -c .\lean_packages\mathlib\.\build\ir\Mathlib\Init\Logic.c
info: .\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:371:16: error: unknown identifier 'decidable_of_decidable_of_iff'
.\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:377:7: error: unknown identifier 'decidable_of_decidable_of_iff'
.\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:455:34: error: function expected at
decidable_of_decidable_of_iff
term has type
?m.10918
.\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:456:38: error: function expected at
decidable_of_decidable_of_iff
term has type
?m.10918
.\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:453:6: error: (kernel) declaration has metavariables 'if_ctx_simp_congr_prop'
.\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:460:34: error: function expected at
decidable_of_decidable_of_iff
term has type
?m.11110
.\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:461:1: error: unknown identifier 'if_ctx_simp_congr_prop'
.\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:458:6: error: (kernel) declaration has metavariables 'if_simp_congr_prop'
.\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:480:44: error: function expected at
decidable_of_decidable_of_iff
term has type
?m.11607
.\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:481:28: error: function expected at
decidable_of_decidable_of_iff
term has type
?m.11607
.\lean_packages\mathlib\.\.\Mathlib\Init\Logic.lean:475:6: error: (kernel) declaration has metavariables 'dif_ctx_simp_congr'
error: external command c:\Users\vatis\.elan\toolchains\leanprover--lean4---nightly\bin\lean.exe exited with status 1
error: build failed
its honestly really annoying tbh
Leonardo de Moura (Mar 13 2022 at 16:17):
This is the latest https://github.com/leanprover/lean4-nightly/releases/tag/nightly-2022-03-13
I just added your example to the test suite: https://github.com/leanprover/lean4/commit/672a889c83cfc00a07a78a28a9d9d99ae1a41162
Joseph O (Mar 13 2022 at 16:19):
It works now, thanks!
Leonardo de Moura (Mar 13 2022 at 16:20):
Joseph O said:
I also have to use the same version as mathlib, or else I get errors.
Recent changes in Lean seem to have broken Mathlib 4. It will be fixed soon.
Mario Carneiro (Mar 13 2022 at 17:30):
Mathlib4 has been updated to today's nightly now
Joseph O (Mar 13 2022 at 17:57):
Small questions:
- What does
Nat.lt_of_succ_lt_succ
do, and why was it used here| a::as, ⟨i+1, h⟩ => get as ⟨i, Nat.lt_of_succ_lt_succ h⟩
? - What does
Nat.succ_pos
do? - Why did lean need this instance,
instance : OfNat (Fin' (List.length (a :: as))) (nat_lit 0) :=
⟨⟨0, (Nat.succ_pos _ : Nat.lt 0 (List.length (a :: as)))⟩⟩
and how exactly in the second argument of the Fin'
working in this instance?
4
Joseph O (Mar 13 2022 at 17:57):
Sorry for all the questions, but Im quite new to this way of programming
Joseph O (Mar 13 2022 at 18:37):
If only the functions had docstrings. But that's ok.
Joseph O (Mar 13 2022 at 18:38):
The core team is doing so much work already :clap:
Henrik Böving (Mar 13 2022 at 18:39):
Joseph O said:
If only the functions had docstrings. But that's ok.
Awaiting your diff :stuck_out_tongue_wink:
Joseph O (Mar 13 2022 at 18:40):
I would if I knew what they did lol
James Gallicchio (Mar 13 2022 at 21:22):
Joseph O said:
What does
Nat.lt_of_succ_lt_succ
do, and why was it used here| a::as, ⟨i+1, h⟩ => get as ⟨i, Nat.lt_of_succ_lt_succ h⟩
?
The type of Nat.lt_of_succ_lt_succ
should tell you everything about it; it just says that n + 1 < m + 1
implies n < m
.
Similar for Nat.succ_pos
, the type gives you all the information.
get
expects something of type Fin as.length
, which is a structure that contains a Nat
and a proof that this nat is < as.length
Kevin Buzzard (Mar 13 2022 at 21:48):
In mathlib we give docstrings to all our definitions but to very few of our proofs for precisely this reason: the type of the proof tells you what it's a proof of.
Joseph O (Mar 14 2022 at 00:25):
Ok, but the question unanswered: Why did lean need this instance?
Mario Carneiro (Mar 14 2022 at 02:26):
Refer back to here. The OfNat (Fin' (List.length (a :: as))) (nat_lit 0)
instance means that when interpreting 0
as an element of Fin' (List.length (a :: as))
it will use this instance to supply the value
Mario Carneiro (Mar 14 2022 at 02:27):
without it, 0 : Fin' (List.length (a :: as))
will not typecheck
Mario Carneiro (Mar 14 2022 at 02:28):
and with it, this expands to ⟨0, (Nat.succ_pos _ : Nat.lt 0 (List.length (a :: as)))⟩ : Fin' (List.length (a :: as))
Joseph O (Mar 14 2022 at 02:32):
Ah I see. But what about any other literal?
Joseph O (Mar 14 2022 at 02:33):
I remember that they are handled.
Mario Carneiro (Mar 14 2022 at 03:09):
No other literal will work in the type Fin' (List.length (a :: as))
, as you can observe
Mario Carneiro (Mar 14 2022 at 03:10):
Like Leo said, this instance only works for 0
Mario Carneiro (Mar 14 2022 at 03:10):
And indeed it wouldn't be reasonable for any other literal to work because 1
might not actually be in the type if as
is the empty list
Mario Carneiro (Mar 14 2022 at 03:11):
if as = []
then this type is Fin' 1
which only has a 0
Mario Carneiro (Mar 14 2022 at 03:12):
So maybe you want an instance for 1 : Fin' (List.length (a :: b :: as))
as well, and this just gets back to why this is a terrible idea
Joseph O (Mar 14 2022 at 12:10):
So then why did my code work?
Joseph O (Mar 14 2022 at 12:13):
I remember I made a function with a generic type that needed to be added, and it need an [OfNat a 0]
instance as well. Why does it only need to know 0? Is this all the information it needs? What does the OfNat
instance tell about a type?
Joseph O (Mar 14 2022 at 12:14):
Mario Carneiro said:
No other literal will work in the type
Fin' (List.length (a :: as))
, as you can observe
Besides 0?
Joseph O (Mar 14 2022 at 19:30):
Ok so i was trying to decipher this part of one of the instances:
Nat.lt_of_not_le fun h => ne_of_gt IsPos.pos (Nat.sub_eq_zero_iff_le.2 h)
So far I have:
Nat.lt_of_not_le takes in two nats and a proof of ~ a <= b
, and returns a proof that b < a
. I don't know where you are giving in the two nats, but fun h => ne_of_gt IsPos.pos (Nat.sub_eq_zero_iff_le.2 h)
is the proof of ~ a <= b
, which is evident from the type signature n ≤ i → False
(I have no idea how the n
and i
get in the signature. In the body, you then prove that a /= b
by giving in a proof of b < a
to Nat.ne_of_gt
(where are you giving in the two nats). I don't know what the IsPos.pos
arg is doing there, as it is only proving that 0 < n
, and after that you are taking the second element of Nat.sub_eq_zero_iff_le
which is Nat.sub_eq_zero_of_le
and giving in h which I have which has a signature of n <= i
, which we are trying to prove false. in short, i have no idea how this is working
Ruben Van de Velde (Mar 14 2022 at 19:47):
First thing you need to know is that lean has implicit arguments, so if you give ~ a <= b
to Nat.lt_of_not_le
, it can figure out the a
and b
itself
Ruben Van de Velde (Mar 14 2022 at 19:55):
If h
is a term of type n <= i
, then Nat.sub_eq_zero_iff_le.2 h
is a term of type n - i = 0
.
ne_of_gt
takes a term of type x > y
(not sure what they are here) and gives a term of type x /= y
or x = y → false
; if you then apply that to an equality (n - i = 0
), you get false
.
So I'm guessing x = n - i
, y = 0
, so IsPos.pos
must be x - i > 0
?
Joseph O (Mar 14 2022 at 19:56):
yeah
Ruben Van de Velde (Mar 14 2022 at 19:56):
Does that connect a few dots? :)
Joseph O (Mar 14 2022 at 19:56):
I have to read it again but yeah
Joseph O (Mar 14 2022 at 19:56):
:)
Arthur Paulino (Mar 14 2022 at 20:04):
offtopic: I remember being mind blown by these crazy types when I only knew simple types like int
, float
, string
etc
Notification Bot (Mar 16 2022 at 00:58):
Joseph O has marked this topic as unresolved.
Joseph O (Mar 16 2022 at 00:59):
Wait, so this whole time
notation:max "#" a:max => (Fin.mk a (by decide))
what the equivalent of
def Fin' (n : Nat) := Fin n
variable (a : α) (as : List α)
instance : OfNat (Fin' (List.length (a :: as))) (nat_lit 0) :=
⟨⟨0, (Nat.succ_pos _ : Nat.lt 0 (List.length (a :: as)))⟩⟩
class IsPos (n : Nat) := pos : 0 < n
instance : IsPos (Nat.succ n) := ⟨Nat.succ_pos _⟩
instance [IsPos (n - i)] : OfNat (Fin' n) i :=
⟨i, Nat.lt_of_not_le fun h => ne_of_gt IsPos.pos (Nat.sub_eq_zero_iff_le.2 h)⟩
:rofl:
Joseph O (Mar 16 2022 at 00:59):
Ruben Van de Velde said:
If
h
is a term of typen <= i
, thenNat.sub_eq_zero_iff_le.2 h
is a term of typen - i = 0
.
ne_of_gt
takes a term of typex > y
(not sure what they are here) and gives a term of typex /= y
orx = y → false
; if you then apply that to an equality (n - i = 0
), you getfalse
.
So I'm guessingx = n - i
,y = 0
, soIsPos.pos
must bex - i > 0
?
yeah so now the only thing i don't understand is
ne_of_gt IsPos.pos (Nat.sub_eq_zero_iff_le.2 h)
James Gallicchio (Mar 16 2022 at 01:55):
What is your question about it?
Ruben Van de Velde (Mar 16 2022 at 06:27):
Do you know that f a b
is actually (f a) b
, where f is a function that takes a and returns another function that takes b?
Also, that x ≠ y
is really a function that takes a proof of x = y
to false?
Last updated: Dec 20 2023 at 11:08 UTC