website/content/blog/writing-unit-tests-lean-4.md
2024-08-05 21:36:20 -07:00

82 lines
2.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "Writing Unit Tests in Lean 4"
date: 2024-08-05T20:43:52-07:00
draft: false
tags: []
math: false
medium_enabled: false
---
When writing Lean code, it's easier to iterate with unit tests than to prove properties about the function off the bat.
I mean if the unit test doesn't even pass, why bother with the proof?
Luckily, Lean 4 let's us do unit tests with a cool command called `#guard`
```lean4
#guard 1 = 1
```
This checks whether the following expression evaluates to `true`. Note that this does not provide a proof since this check is done using the untrusted evaluator.
What does need to be proven, however, is that the expression given is decidable.
One issue I faced is that I often use the `Except` type for error handling in my code.
```lean4
inductive Except (ε : Type u) (α : Type v) where
/-- A failure value of type `ε` -/
| error : ε → Except ε α
/-- A success value of type `α` -/
| ok : α → Except ε α
```
For a simple (somewhat silly) example, look at the following function
```lean4
def gt_0 (n : Nat) : Except String Bool :=
if n = 0 then .error s!"{n} is not greater than zero"
else .ok true
```
We can evaluate our function on a given input:
```lean4
#eval gt_0 1 -- Except.ok true
```
However, we can't guard against it without encountering an error. This is because we haven't shown that the equality is decidable.
To help with this, I wrote up a function that we can apply generically.
```lean4
def Except.deq [DecidableEq α] [DecidableEq β] : DecidableEq (Except α β) := by
unfold DecidableEq
intro a b
cases a <;> cases b <;>
-- Get rid of obvious cases where .ok != .err
try { apply isFalse ; intro h ; injection h }
case error.error c d =>
match decEq c d with
| isTrue h => apply isTrue (by rw [h])
| isFalse _ => apply isFalse (by intro h; injection h; contradiction)
case ok.ok c d =>
match decEq c d with
| isTrue h => apply isTrue (by rw [h])
| isFalse _ => apply isFalse (by intro h; injection h; contradiction)
```
We can then show that equality of `Except String Bool` is decidable, since Lean already knows that string and boolean equality is decidable.
```lean4
instance: DecidableEq (Except String Bool) := Except.deq
```
From here, we can use `#guard`
```lean4
#guard gt_0 1 = (Except.ok true)
```
When a `#guard` fails, it will throw an error during compilation. This is great for ensuring that our unit tests pass during compilation.