Subtraction: example of intertwined proofs and definitions. #
The difference of natural numbers is not a natural number. We see three ways of overcoming this problem, which illustrate various important concepts in Lean.
The first two are in this module while the third is in a sequel. The first two are in fact available in functional programming langauges such as
To begin with, we see how Lean deals with subtraction on
#eval 4 - 3 -- 1 #eval 3 - 4 -- 0
The first example is as expected, but the second may be surprising. According to the documentation of Lean 4,
(Truncated) subtraction of natural numbers. Because natural numbers are not closed under subtraction, we define
m - nto be 0 when
n < m.
Subtraction with panic #
Our first remedy is to define subtraction as Lean does but with an error message when the result is incorrect.
Subtraction of natural numbers; panics when difference is negative.
A brief digression: Lean 4 lets us easily introduce new notation for our variant of subtraction.
infix:64 "-!" => Nat.sub!
More on panicking #
The intriguing fact about the illegal subtraction is that, while it gave an error, it still had a value. Indeed, we see more of the underlying phenomenon in the following examples.
def panicNat : ℕ := panic! "I like to panic" #check panicNat -- ℕ -- #eval panicNat -- 0 (with error)
Indeed, if we try to make an analogous definition for
Empty we get an error.
gives the error message:
Indeed the empty type has no inhabitants so allowing a definition of
badPanic would be a contradiction.
Default values and typeclasses #
The value returned when panicing is the
default value of the type. Not every type has a default value. For example, the
Empty type has no default value.
Default values can be synthesized from other default values by so called typeclass inference. First we see some examples of default values.
#eval defaultNat -- 0
As we have seen earlier, the default value of
As we saw in the case of panic, the default value of
Empty is not defined. The following gives an error message.
def defaultEmpty : Empty := default
A more interesting example is the default value of a product type.
#eval default₁ -- (0, 0)
This is inferred from the default values of the components.
We sketch the basic ideas of typeclasses and how they are used here. The
default value of a type
α is based on
Some more examples of typeclass inference.
#reduce default₂ -- (Nat.zero, "", fun x => Nat.zero) #reduce default₃ -- (Nat.zero, fun x => Nat.zero, fun a => False.rec (fun x => Empty) (_ : False))
In the first example, we see that default functions are inferred if the codomains are inhabited, with a constant function used as a default.
Lean has inferred a default function from
Empty by using the default function from
False. To illustrate introducing new defaults we introduce a new type
MyEmpty which is also an empty type.
We see this picked up in the following construction. Note that defining a default for
MyEmpty gives an error.
#reduce default₄ -- (Nat.zero, fun x => Nat.zero, fun x a => a)
The following example shows the effect of priorities of instances.
#reduce default₆ -- (Nat.zero, fun x => Nat.zero, fun x a => a)
The second choice is to essentially return values only when they are valid, by wrapping them in an
Some examples of subtraction returning option types.
#eval 4 -? 3 -- some 1 #eval 3 -? 4 -- none
If we return option types we need to be able to handle them. We illustrate this by defining a function that returns the double of the difference if it is defined.
def Nat.doubleSub? (m n : ℕ) : Option ℕ := (m -? n).map (· * 2) #eval Nat.doubleSub? 5 3 -- some 4 #eval Nat.doubleSub? 5 32 -- none
A convenient way to handle option types is to use the
def Nat.tripleSub? (m n : ℕ) : Option ℕ := do let d ← m -? n return d * 3 #eval Nat.tripleSub? 5 3 -- some 6