Zulip Chat Archive
Stream: new members
Topic: Making Fin error on out-of-range number instead of wrapping
MrQubo (Jan 23 2025 at 23:55):
Is it possible to have type like Fin
, but without modular arithmetics?
E.g. I would expect that to not compile:
#eval (3 : Fin 2)
#eval ((1 : Fin 2) + (1 : Fin 2) : Fin 2)
In other words, I want Fin n
to be a number i
with a proof that 0 ≤ i < n
.
Actually, I was mislead by the documentation of Fin
. It does not mention it having modular arithmetic at all.
I could write {n : Nat // 0 ≤ n ∧ n < 4}
, but I would prefer being able to construct the type directly from integer literals, and having implicit conversion to Nat.
Aaron Liu (Jan 24 2025 at 00:06):
Why? What's your use case? Usually we want functions to be total, which is why 1 / 0
gives 0
.
MrQubo (Jan 24 2025 at 09:05):
In my case structure is well-defined only for numbers in the range. I also find it very weird that this works:
def x : Nat := 123
#eval @List.Vector.get Nat 3 ⟨[1, 2, 3], by simp⟩ x
i.e. the index wraps because it uses Fin. With List.Vector I expected to have compile-time checks of index, not wrapping behaviour.
Ruben Van de Velde (Jan 24 2025 at 11:12):
So the issue here is with numeric literals. You can write your Fin
values as Fin.mk 3 (proof that 3 < n)
MrQubo (Jan 24 2025 at 12:04):
Ah, I see it now. So e.g. this doesn't work
def vec_snd {α n} (l : List.Vector α n) (h : n ≥ 2) : α :=
List.Vector.get l 1
and I need to use ⟨1, h⟩
.
I still think that wrapping behaviour should be removed. But I guess in most cases n
won't be a literal so doesn't matter that much.
MrQubo (Jan 24 2025 at 12:48):
Actually, not true. It applies not only to numeric literals. It also applies to arithmetics, and even .succ
.
Example, this typechecks:
inductive Lam : ∀ (_ : Nat), Type
| bvar : ∀ {n}, (m : Nat) → (_ : m < n := by omega) → Lam n
| fvar : ∀ {n}, String → Lam n
| app : ∀ {n}, Lam n → Lam n → Lam n
| lam : ∀ {n}, Lam n.succ → Lam n
deriving Repr
open Lam
def renum_bvars {n} (t : Lam n) (i : Fin n.succ := 0) : Lam n.succ :=
match t with
| bvar m _ =>
if m ≥ i
then bvar m.succ
else bvar m
| fvar name => fvar name
| app f arg => app (renum_bvars f i) (renum_bvars arg i)
| lam body => lam (@renum_bvars n.succ body i.succ.succ)
I can write this to avoid modular arithmetic:
def renum_bvars {n} (t : Lam n) (i : Fin n.succ := 0) : Lam n.succ :=
match t with
| bvar m _ =>
if m ≥ i
then bvar m.succ
else bvar m
| fvar name => fvar name
| app f arg => app (renum_bvars f i) (renum_bvars arg i)
| lam body => lam (@renum_bvars n.succ body ⟨i.val.succ, by simp⟩)
Aaron Liu (Jan 24 2025 at 12:59):
docs#Fin.succ takes a Fin n
and returns a Fin (n + 1)
.
Aaron Liu (Jan 24 2025 at 13:02):
MrQubo said:
Example, this typechecks:
inductive Lam : ∀ (_ : Nat), Type | bvar : ∀ {n}, (m : Nat) → (_ : m < n := by omega) → Lam n | fvar : ∀ {n}, String → Lam n | app : ∀ {n}, Lam n → Lam n → Lam n | lam : ∀ {n}, Lam n.succ → Lam n deriving Repr open Lam def renum_bvars {n} (t : Lam n) (i : Fin n.succ := 0) : Lam n.succ := match t with | bvar m _ => if m ≥ i then bvar m.succ else bvar m | fvar name => fvar name | app f arg => app (renum_bvars f i) (renum_bvars arg i) | lam body => lam (@renum_bvars n.succ body i.succ.succ)
Not for me! Am I doing something wrong or did you just not notice the
application type mismatch
renum_bvars body (Fin.succ i).succ
argument
(Fin.succ i).succ
has type
Fin (n.succ + 1 + 1) : Type
but is expected to have type
optParam (Fin n.succ.succ) 0 : Type
Edward van de Meent (Jan 24 2025 at 13:10):
the "no modular arithmetic" code uses only one succ
, that fixes it
MrQubo (Jan 24 2025 at 13:12):
@Aaron Liu Indeed, doesn't typechecks on nightly. Thanks!
I'm on
Lean 4.16.0-rc2
Target: x86_64-unknown-linux-gnu
and it does typecheck.
MrQubo (Jan 24 2025 at 13:15):
Edward van de Meent said:
the "no modular arithmetic" code uses only one
succ
, that fixes it
But the point was, that the code with .succ.succ
also "worked" (in a sense that it was typechecking).
Edward van de Meent (Jan 24 2025 at 13:21):
that's weird, it doesn't typecheck on the playground with
Lean 4.16.0-rc2
Target: x86_64-unknown-linux-gnu
Edward van de Meent (Jan 24 2025 at 13:21):
maybe you need to refresh your editor?
Mitchell Lee (Jan 25 2025 at 02:18):
If you don't want addition on Fin 2
to wrap around, then the solution is to not add two elements of Fin 2
if the result would be big enough to wrap around.
It is impossible to redesign the addition on Fin 2
in such a way that the line of code ((1 : Fin 2) + (1 : Fin 2) : Fin 2)
is a type error. This is because Lean's type checking system has no way of knowing whether the numbers you are adding are big enough to wrap around or not.
Mitchell Lee (Jan 25 2025 at 02:20):
The documentation of Fin
does not mention modular arithmetic because the definition of Fin
, by itself, does not include any arithmetic operations. The arithmetic operations are defined separately. For example, see docs#Fin.add, whose documentation does mention modular arithmetic.
MrQubo (Jan 26 2025 at 00:49):
This is because Lean's type checking system has no way of knowing whether the numbers you are adding are big enough to wrap around or not.
That's not true, or I don't understand what you mean.
Currently I'm using {i : Nat // i < n}
instead of Fin n
, and it does work like I want. It's a bit more cumbersome to use though. So yes, I can check during compilation whether addition will wrap over n
or not. Or, in this particular case, verify that it never won't, as that's what I need.
My main concern is that, the name Fin
is not suitable for what that type does. Mod
would be much better IMO.
And my question is that, is there a type in lean or mathlib for {i : Nat // i < n}
?
Yakov Pechersky (Jan 26 2025 at 01:19):
What is the Add instance you've defined on your type?
Kevin Buzzard (Jan 26 2025 at 01:43):
MrQubo said:
And my question is that, is there a type in lean or mathlib for
{i : Nat // i < n}
?
Yes, that type is Fin n
. It just has an addition on it that you don't like.
The prevailing philosophy in library design here is that it's more convenient in practice to output junk values given junk inputs rather than to complain that things aren't possible (or equivalently to demand proofs from the user). This is the same reason that we have 1 / 0 = 0 as opposed to demanding a proof that you're not dividing by 0 or complaining that the division isn't possible.
Mitchell Lee (Jan 26 2025 at 09:20):
It is worth noting that if Fin
were changed to not use modular arithmetic, a sizable part of Lean's core library would have to be reworked. So, no, it is not really possible to change it at this point. Just use Fin n
. You do not have to use the addition operation if you do not want to.
Also, it is worth noting that many programming languages, including C++ which Lean relies on, use modular arithmetic (specifically, arithmetic modulo , , , and ) for their bounded integer types, because CPUs are especially good at it.
MrQubo (Jan 26 2025 at 11:26):
Kevin Buzzard said:
MrQubo said:
And my question is that, is there a type in lean or mathlib for
{i : Nat // i < n}
?Yes, that type is
Fin n
. It just has an addition on it that you don't like.The prevailing philosophy in library design here is that it's more convenient in practice to output junk values given junk inputs rather than to complain that things aren't possible (or equivalently to demand proofs from the user). This is the same reason that we have 1 / 0 = 0 as opposed to demanding a proof that you're not dividing by 0 or complaining that the division isn't possible.
That makes sense. I guess it makes you worry less about proofs while programming, and only while writing a proof you might end up having to consider a case of 1 / 0
e.g.
MrQubo (Jan 26 2025 at 11:28):
Yakov Pechersky said:
What is the Add instance you've defined on your type?
I've tried defining addition for it, but no luck. I'm just doing addition on Nat, and constructing the proof separately. Thanks to autoProps, as mentioned earlier in this thread, most proofs are constructed automatically.
Last updated: May 02 2025 at 03:31 UTC