lean4-htt/tests/lean/run/partial_fixpoint_coinductive_pred.lean
Joachim Breitner 7b813d4f5d
feat: partial_fixpoint: partial functions with equations (#6355)
This PR adds the ability to define possibly non-terminating functions
and still be able to reason about them equationally, as long as they are
tail-recursive or monadic.

Typical uses of this feature are
```lean4
def ack : (n m : Nat) → Option Nat
  | 0,   y   => some (y+1)
  | x+1, 0   => ack x 1
  | x+1, y+1 => do ack x (← ack (x+1) y)
partial_fixpiont

def whileSome (f : α → Option α) (x : α) : α :=
  match f x with
  | none => x
  | some x' => whileSome f x'
partial_fixpiont

def computeLfp {α : Type u} [DecidableEq α] (f : α → α) (x : α) : α :=
  let next := f x
  if x ≠ next then
    computeLfp f next
  else
    x
partial_fixpiont

noncomputable def geom : Distr Nat := do
  let head ← coin
  if head then
    return 0
  else
    let n ← geom
    return (n + 1)
partial_fixpiont
```

This PR contains

* The necessary fragment of domain theory, up to (a variant of)
Knaster–Tarski theorem (merged as
https://github.com/leanprover/lean4/pull/6477)
* A tactic to solve monotonicity goals compositionally (a bit like
mathlib’s `fun_prop`) (merged as
https://github.com/leanprover/lean4/pull/6506)
* An attribute to extend that tactic (merged as
https://github.com/leanprover/lean4/pull/6506)
* A “derecursifier” that uses that machinery to define recursive
function, including support for dependent functions and mutual
recursion.
* Fixed-point induction principles (technical, tedious to use)
* For `Option`-valued functions: Partial correctness induction theorems
that hide all the domain theory

This is heavily inspired by [Isabelle’s `partial_function`
command](https://isabelle.in.tum.de/doc/codegen.pdf).
2025-01-21 09:54:30 +00:00

97 lines
3.3 KiB
Text
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.

/-!
Johannes Hölzl pointed out that the `partial_fixpoint` machinery can be applied to `Prop` to define
inductive or (when using the dual order) coinductive predicates.
Without an induction principle this isn't so useful, though.
-/
open Lean.Order
instance : PartialOrder Prop where
rel x y := y → x -- NB: Dual
rel_refl := fun x => x
rel_trans h₁ h₂ := fun x => h₁ (h₂ x)
rel_antisymm h₁ h₂ := propext ⟨h₂, h₁⟩
instance : CCPO Prop where
csup c := ∀ p, c p → p
csup_spec := fun _ =>
⟨fun h y hcy hx => h hx y hcy, fun h hx y hcy => h y hcy hx ⟩
@[partial_fixpoint_monotone] theorem monotone_exists
{α} [PartialOrder α] {β} (f : α → β → Prop)
(h : monotone f) :
monotone (fun x => Exists (f x)) :=
fun x y hxy ⟨w, hw⟩ => ⟨w, monotone_apply w f h x y hxy hw⟩
@[partial_fixpoint_monotone] theorem monotone_and
{α} [PartialOrder α] (f₁ : α → Prop) (f₂ : α → Prop)
(h₁ : monotone f₁) (h₂ : monotone f₂) :
monotone (fun x => f₁ x ∧ f₂ x) :=
fun x y hxy ⟨hfx₁, hfx₂⟩ => ⟨h₁ x y hxy hfx₁, h₂ x y hxy hfx₂⟩
def univ (n : Nat) : Prop :=
univ (n + 1)
partial_fixpoint
/-
The following models a coinductive predicate defined as
```
coinductive infinite_chain step : α → Prop where
| intro : ∀ y x, step x = some y → infinite_chain step y → infinite_chain step
```
-/
def infinite_chain {α} (step : α → Option α) (x : α) : Prop :=
∃ y, step x = some y ∧ infinite_chain step y
partial_fixpoint
theorem infinite_chain.intro {α} (step : α → Option α) (y x : α) :
step x = some y → infinite_chain step y → infinite_chain step x := by
intros; unfold infinite_chain; simp [*]
theorem infinite_chain.coinduct {α} (P : α → Prop) (step : α → Option α)
(h : ∀ (x : α), P x → ∃ y, step x = some y ∧ P y) :
∀ x, P x → infinite_chain step x := by
apply infinite_chain.fixpoint_induct step
(motive := fun i => ∀ (x : α), P x → i x)
case adm =>
clear h step
apply admissible_pi
intro a
intro c hchain h hPa Q ⟨f, hcf, hfaQ⟩
subst Q
apply h f hcf hPa
case h =>
intro ic hPic x hPx
obtain ⟨y, hstepx, h⟩ := h x hPx
exact ⟨y, hstepx, hPic y h⟩
/--
Isabelle generates a stronger coinduction theorem from
```
coinductive infinite_chain :: "('a ⇒ 'a option) ⇒ 'a ⇒ bool" for step :: "'a ⇒ 'a option" where
intro: "infinite_chain step x" if "step x = Some y" and "infinite_chain step y"
```
Note the occurrence of `infinite_chain` in the step:
```
Scratch.infinite_chain.coinduct:
?X ?x ⟹
(⋀x. ?X x ⟹ ∃xa y. x = xa ∧ ?step xa = Some y ∧ (?X y infinite_chain ?step y)) ⟹
infinite_chain ?step ?x
```
We can prove that from the one above.
-/
theorem infinite_chain.coinduct_strong {α} (P : α → Prop) (step : α → Option α)
(h : ∀ (x : α), P x → ∃ y, step x = some y ∧ (P y infinite_chain step y)) :
∀ x, P x → infinite_chain step x := by
suffices ∀ x, (P x infinite_chain step x) → infinite_chain step x by
intro x hPx
exact this x (.inl hPx)
apply infinite_chain.coinduct
intro x hor
cases hor
next hPx => exact h _ hPx
next hicx =>
unfold infinite_chain at hicx
obtain ⟨y, hstepx, h⟩ := hicx
exact ⟨y, hstepx, .inr h⟩