Zulip Chat Archive

Stream: mathlib4

Topic: Mathlib4 `calc` issue?


David Pearce (May 13 2024 at 21:09):

Hi, I was working through converting a proof to calc style and came across this strangeness:

--import Mathlib.Tactic

def pow256_shr (n : Nat) (m : UInt8) (k : Nat) (p : n < 256^k) : (m.val * 256^k + n < 256^(k+1)) := by
  have mLt : (m.val : Nat) < 256 := m.val.isLt
  calc
    _ < 256 * 256^k := by sorry
    _ = 256^(k+1) := by sorry

On Lean 4.7.0 the above succeeds (ignoring sorry) for now. However, if I uncomment the import Mathlib.Tactic line, then it fails. Here, Mathlib is imported with this line in the lakefile.lean:

require mathlib from git "https://github.com/leanprover-community/mathlib4"@"v4.7.0"

I'm confused what's going on here?

Eric Wieser (May 13 2024 at 21:26):

This works:

import Mathlib.Tactic
def pow256_shr (n : Nat) (m : UInt8) (k : Nat) (p : n < 256^k) :
    (m.val.val * 256^k + n < 256^(k+1)) := by
       -- ^^^^ added this
  have mLt : (m.val : Nat) < 256 := m.val.isLt
  calc
    _ < 256 * 256^k := by sorry
    _ = 256^(k+1) := by sorry

Eric Wieser (May 13 2024 at 21:26):

The issue is that the new imports changed the meaning of the theorem!

Patrick Massot (May 13 2024 at 21:30):

I think that not providing the LHS on the first calc line is also not helping Lean.

Eric Wieser (May 13 2024 at 21:31):

It's already too late by then though, the statement was wrong as soon as the by began

Eric Wieser (May 13 2024 at 21:31):

With Mathlib.Tactic imported, the type of the 256 is now Fin 256 not Nat; and so the theorem is false!

Patrick Massot (May 13 2024 at 21:31):

Sure, these are independent comments.

Eric Wieser (May 13 2024 at 21:32):

I've not seen an algebraic case where dropping the first calc term causes an issue

Eric Wieser (May 13 2024 at 21:32):

(It certainly happens with morphisms, where you have to give the LHS on every line, repeating the RHS of the previous line!)

David Pearce (May 13 2024 at 22:17):

Eric Wieser said:

With Mathlib.Tactic imported, the type of the 256 is now Fin 256 not Nat; and so the theorem is false!

So, this is the key thing. Using Mathlib changes the types given to constants? That seems ... unexpected to me.

Yury G. Kudryashov (May 13 2024 at 22:18):

You don't specify types of LHS and RHS, so Lean has to guess. Mathlib adds more instances, so Lean can put everything to Fin 256 now instead of casting to Nat.

Yury G. Kudryashov (May 13 2024 at 22:19):

Instead of adding extra .val, you can specify that LHS has type Nat.

Kyle Miller (May 14 2024 at 16:19):

The rule for arithmetic is that it looks at the whole arithmetic expression tree, finds the types of everything (if known), and then figures out what everything can be coerced to. It turns out m.val is a Fin UInt8.size, so it goes with that.

It doesn't affect anything here, but this algorithm can go wrong if there's a loop in the coercions. We fixed this with ZMod the last time this discussion came up, but Fin is not well-behaved: (((16 : Nat) : Fin 5) : Nat). We'll have to fix that one...

Kevin Buzzard (May 14 2024 at 18:26):

In my mind the difference between ZMod and Fin is that ZMod has a coercion from Nat but no coercion to Nat (it's the quotient), and Fin has a coercion to Nat but not a coercion from Nat (it's the subobject). However I'm aware that this doesn't correspond to reality -- people want to load more and more structure onto Fin n to get it to be some kind of substitute for ZMod.

Yury G. Kudryashov (May 14 2024 at 23:25):

Currently Fin has coercions both ways.

Yury G. Kudryashov (May 14 2024 at 23:27):

On the one hand, this (and related OfNat instances) allow expressions like (3 : Fin 5). OTOH, sometimes this leads to confusion.

Kyle Miller (May 14 2024 at 23:34):

You don't need the coercion to make expressions like (3 : Fin 5) work though, and the bidirectional coercion causes issues with elaboration:

import Mathlib.Tactic

#eval decide <| (3 : Fin 2) = (3 : Nat)
-- true
#eval decide <| (3 : Nat) = (3 : Fin 2)
-- false

It's not good that this depends on imports. Without the import, both are false.

Kyle Miller (May 14 2024 at 23:36):

This one's because mathlib has docs#Fin.instCommRing, which enables the Nat -> Fin n coercion, but since Fin is a subtype, it has a Fin n -> Nat coercion too.

Kyle Miller (May 14 2024 at 23:38):

Johan had fixed this for ZMod by removing the ZMod n -> Nat coercion here, but that's not an option for Fin. It's meant to be a subtype of the natural numbers.

Kyle Miller (May 14 2024 at 23:57):

Some thoughts about solving this problem:

  • We could make Fin.instCommRing not be a global instance, but that seems heavy handed.
  • We could make the NatCast coercions somehow be opt-in. I'm not sure what that would look like (is it instances you activate? an explicit coercion notation you use?)
  • We could get the core expression tree elaborator to be more picky with what coercions it should consider. There are valid reasons for inserting Fin n -> Nat coercions, but inserting non-injective coercions seems problematic.

Yury G. Kudryashov (May 15 2024 at 02:55):

Fin is a custom structure, not a subtype (though we think about it as a subtype).

Yury G. Kudryashov (May 15 2024 at 02:57):

I would vote for removing Fin.instCommRing. We can have it as a scoped instance (e.g., if someone will write a norm_num extension for Fin)

Yury G. Kudryashov (May 15 2024 at 02:57):

But I like the idea "if you want a ring, use ZMod". Note: I may change my mind when I see the diff.


Last updated: May 02 2025 at 03:31 UTC