Zulip Chat Archive
Stream: lean4
Topic: Lean does not panic when division by zero
Asei Inoue (Oct 31 2025 at 13:49):
In programming languages, it is common to “abort execution and return an error.”
In many languages, including Haskell, dividing by zero causes a runtime error.
However, in Lean, division by zero does not cause an error (at least when using Nat.div).
I’ve read Kevin’s excellent article on why this happens.
It was very clear. As I understand it, the article argues that since functions in Lean are used both as programs and as proofs, it’s better to avoid partial functions.
I’ve been trying to come up with my own way to explain this idea.
I thought of the following explanation:
A function that “crashes midway without returning a value” is, in the sense that it doesn’t return a value, equivalent to a function that loops forever.
Therefore, such a function should be disallowed from the perspective of soundness.
Is this explanation correct?
If it is, would it be possible to actually demonstrate a contradiction by allowing “functions that crash midway without returning a value” using something like the unsafe modifier—just as we can with functions that loop forever when unsafe is allowed?
Kenny Lau (Oct 31 2025 at 13:55):
not sure if I understand you correctly, but I don't think there's any soundness bug here, since docs#panic requires the type to be inhabited
and also I'm not sure if we have a common understanding of how panic works, and how proofs work, because in the alternative universe where division by zero is panic (which is bad for other reasons), it doesn't actually do anything when you're proving theorems, because when you prove theorems you don't usually evaluate the functions inside. For example, notice how the following code does not panic:
def f : Nat := panic "asdf"
example : f = f := rfl
it doesn't even panic when you unfold f! in fact it just unfolds to panic, saying the action is different from doing the action
so actually the only to make it panic is by eval-ing it, and the only way to do that is to do native_decide, i.e.
def f : Nat := panic! "asdf"
example : f = 0 := by
decide +native
Asei Inoue (Oct 31 2025 at 14:11):
@Kenny Lau
I don't think there's any soundness bug here, since docs#panic requires the type to be inhabited
Indeed, if we use Lean’s panic function, then it’s safe.
Even if we don’t use panic, since Nat itself is Inhabited, a function of type Nat → Nat → Nat that crashes midway would still not lead to a contradiction.
I see, that makes sense to me now. Thank you.
Riccardo Brasca (Oct 31 2025 at 14:39):
Lean is very close to mathematical language, and in my opinion this makes things much more clear (the following is surely not 100% accurate): in mathematics there is no such a thing as "crashing" or "being undefined", and in Lean it is the same, a function must have a value. You can use tricks to simulate crashing, like using a default value, but there must is a value. You can of course also change the domain, and this is in theory perfectly doable, but annoying in practice.
Kenny Lau (Oct 31 2025 at 14:44):
@Asei Inoue note that the standard way to represent "crashing" is to add Option to the return type,
def Nat.div? : Nat → Nat → Option Nat :=
fun p q ↦ if q = 0 ∨ p % q ≠ 0 then none else some (p / q)
#eval (List.range 11).map (Nat.div? 10 ·)
Kenny Lau (Oct 31 2025 at 14:45):
but please don't do it in mathlib
Yury G. Kudryashov (Oct 31 2025 at 15:21):
BTW, I think that writing a library on top of Mathlib that use Option or Part/PFun to provide semantics that's closer to the pen&paper math would be a nice project, but I don't have time for it. E.g., this will allow us to express the semantic of "if the RHS is defined, then the LHS is defined and is equal to RHS" or "both sides of an equation are defined and they're equal" (common understanding of equations with square roots etc in high school).
Kenny Lau (Oct 31 2025 at 15:22):
we'll need to bind everything in statements then? we'll end up with monads everywhere
Kenny Lau (Oct 31 2025 at 15:25):
import Mathlib
noncomputable def sqrt? : ℝ → Option ℝ :=
fun x ↦ if 0 ≤ x then √x else none
notation "√?" x => sqrt? x
example : pure 0 < (do √?(1 + (← √? 2))) :=
sorry
Kenny Lau (Oct 31 2025 at 15:26):
the < instance also needs to be changed
Yury G. Kudryashov (Oct 31 2025 at 15:31):
I don't know what's the best way to make it useful/readable. I think that we should convert inputs to Part _ and stay there, but I don't want to spend my time on this. Also, this library should come with a tactic that converts the goal to a Part-free version that can be proved using Mathlib.
Yury G. Kudryashov (Oct 31 2025 at 16:20):
While I think that this would be a nice project for someone relatively new to Lean (probably, under a supervision of someone more experienced), I think that almost any project aiming to formalize a nice theorem is more important.
Trebor Huang (Nov 01 2025 at 01:07):
In particular, is Lean able to non-tautologically state the theorem "The square root of x/(x-1) is defined on the subset (-inf, 0] union (1, +inf)"? What would be a nice way to formalize this kind of statements?
Kenny Lau (Nov 01 2025 at 01:08):
if you follow the above suggestion to use Option, then the statement would say that this expression is Option.IsSome
Kenny Lau (Nov 01 2025 at 01:22):
@Trebor Huang
import Mathlib
#print Part
-- TODO: the two functions below could be made computable
@[simps!] noncomputable def Real.sqrt? : ℝ → Part ℝ :=
fun x ↦ ⟨0 ≤ x, fun _ ↦ √x⟩
@[simps!] noncomputable def Real.div? : ℝ → ℝ → Part ℝ :=
fun x y ↦ ⟨y ≠ 0, fun _ ↦ x / y⟩
notation "√?" x => Real.sqrt? x
notation a " /? " b => Real.div? a b
theorem foo (x : ℝ) : (do √?(← x /? (x - 1))).Dom ↔ x ≤ 0 ∨ 1 < x := by
simp; grind [div_nonneg_iff, sub_eq_zero]
Kenny Lau (Nov 01 2025 at 01:25):
I love grind
Last updated: Dec 20 2025 at 21:32 UTC