Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: partial_fixpoint #253

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cd4014e
Switch to partial fixpoint pr-release
nomeata Jan 16, 2025
212d52d
Reapply "Local setup"
nomeata Jan 16, 2025
168a53b
A bit more
nomeata Jan 16, 2025
3fd2703
5
nomeata Jan 16, 2025
59287ee
fixes
nomeata Jan 16, 2025
3c61ae9
Shorter lines
nomeata Jan 16, 2025
cce70f5
Copy editing
nomeata Jan 16, 2025
3c3d32e
Typos
nomeata Jan 16, 2025
fe2a45b
More typos
nomeata Jan 16, 2025
1b87068
Even more typos
nomeata Jan 16, 2025
ccdb638
More on theory
nomeata Jan 17, 2025
898660a
Render as plain text
nomeata Jan 17, 2025
e18cb0c
Less imports
nomeata Jan 17, 2025
56b0219
Try to use a table (need help with the error)
nomeata Jan 17, 2025
0075820
Revert "Try to use a table (need help with the error)"
nomeata Jan 17, 2025
ea47ac5
Reapply "Try to use a table (need help with the error)"
nomeata Jan 20, 2025
f7f8c42
Get table to work
nomeata Jan 20, 2025
e16ca45
More on the table
nomeata Jan 20, 2025
2d55e63
Merge branch 'main' of github.com:leanprover/reference-manual into jo…
nomeata Jan 20, 2025
7c36218
Use {name} not {inst}
nomeata Jan 20, 2025
3709142
cargo-cult local syntax tricks
nomeata Jan 20, 2025
37020b8
Copy editing
nomeata Jan 20, 2025
7c0d7ad
A (not very interesting) mutual recursion section
nomeata Jan 20, 2025
95a0625
Typo
nomeata Jan 20, 2025
39f81f1
Avoid duplicate anchor
nomeata Jan 20, 2025
8478f38
Use ppExpr
nomeata Jan 20, 2025
5b8409a
Bump to nightly
nomeata Jan 22, 2025
13f0ba9
Make build work with nightly
nomeata Jan 22, 2025
5bb05ce
Proofreading and minor style
david-christiansen Jan 24, 2025
e35470c
render monotonicity lemmas with highlighting
david-christiansen Jan 24, 2025
497bd7f
remove obsolete TODO
david-christiansen Jan 24, 2025
29c7ccd
Merge remote-tracking branch 'upstream/main' into joachim/partial-fix…
david-christiansen Jan 30, 2025
10ec009
Merge remote-tracking branch 'upstream/main' into joachim/partial-fix…
david-christiansen Jan 31, 2025
184fe73
Fix typo and repeated word
david-christiansen Jan 31, 2025
eb07a0f
Merge remote-tracking branch 'upstream/main' into joachim/partial-fix…
david-christiansen Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
2 changes: 2 additions & 0 deletions .vale/styles/config/ignore/terms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ equational
executable's
extensional
extensionality
fixpoint
fixpoints
functor
functor's
functors
Expand Down
113 changes: 113 additions & 0 deletions Manual/Meta/Monotonicity.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/-
Copyright (c) 2025 Lean FRO LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Joachim Breitner
-/

import Verso

import Manual.Meta.Attribute
import Manual.Meta.Basic
import Manual.Meta.Lean
import Manual.Meta.Table

open Lean Meta Elab
open Verso Doc Elab Manual
open SubVerso.Highlighting Highlighted


namespace Manual

/--
A table for monotonicity lemmas. Likely some of this logic can be extracted to a helper
in `Manual/Meta/Table.lean`.
-/
private def mkInlineTable (rows : Array (Array Term)) : TermElabM Term := do
if h : rows.size = 0 then
throwError "Expected at least one row"
else
let columns := rows[0].size
if columns = 0 then
throwError "Expected at least one column"
if rows.any (·.size != columns) then
throwError s!"Expected all rows to have same number of columns, but got {rows.map (·.size)}"

let blocks : Array Term :=
#[ ← ``(Inline.text "Theorem"), ← ``(Inline.text "Pattern") ] ++
rows.flatten
``(Block.other (Block.table $(quote columns) (header := true) Option.none Option.none)
#[Block.ul #[$[Verso.Doc.ListItem.mk #[Block.para #[$blocks]]],*]])


section delabhelpers

/-!
To format the monotonicy lemma patterns, I’d like to clearly mark the monotone arguments from
the other arguments. So I define two gadgets with custom delaborators.
-/

def monoArg := @id
def otherArg := @id

open PrettyPrinter.Delaborator

@[app_delab monoArg] def delabMonoArg : Delab :=
PrettyPrinter.Delaborator.withOverApp 2 `(·)
@[app_delab otherArg] def delabOtherArg : Delab :=
PrettyPrinter.Delaborator.withOverApp 2 `(_)

end delabhelpers



@[block_role_expander monotonicityLemmas]
def monotonicityLemmas : BlockRoleExpander
| #[], #[] => do
let names := (Meta.Monotonicity.monotoneExt.getState (← getEnv)).values
let names := names.qsort (toString · < toString ·)

let rows : Array (Array Term) ← names.mapM fun name => do
-- Extract the target pattern
let ci ← getConstInfo name

-- Omit the `Lean.Order` namespace, if present, to keep the table concise
let nameStr := (name.replacePrefix `Lean.Order .anonymous).getString!
let hl : Highlighted ← constTok name nameStr
let nameStx ← `(Inline.other {Inline.name with data := ToJson.toJson $(quote hl)}
#[Inline.code $(quote nameStr)])

let patternStx : TSyntax `term ←
forallTelescope ci.type fun _ concl => do
unless concl.isAppOfArity ``Lean.Order.monotone 5 do
throwError "Unexpected conclusion of {name}"
let f := concl.appArg!
unless f.isLambda do
throwError "Unexpected conclusion of {name}"
lambdaBoundedTelescope f 1 fun x call => do
-- Monotone arguments are the free variables applied to `x`,
-- Other arguments the other
-- This is an ad-hoc transformation and may fail in cases more complex
-- than we need right now (e.g. binders in the goal).
let call' ← Meta.transform call (pre := fun e => do
if e.isApp && e.appFn!.isFVar && e.appArg! == x[0]! then
.done <$> mkAppM ``monoArg #[e]
else if e.isFVar then
.done <$> mkAppM ``otherArg #[e]
else
pure .continue)

let hlCall ← withOptions (·.setBool `pp.tagAppFns true) do
let fmt ← Lean.Widget.ppExprTagged call'
renderTagged none fmt ⟨{}, false⟩
let fmt ← ppExpr call'
``(Inline.other (Inline.lean $(quote hlCall)) #[(Inline.code $(quote fmt.pretty))])

pure #[nameStx, patternStx]

let tableStx ← mkInlineTable rows
return #[tableStx]
| _, _ => throwError "Unexpected arguments"

-- #eval do
-- let (ss, _) ← (monotonicityLemmas #[] #[]).run {} (.init .missing)
-- logInfo (ss[0]!.raw.prettyPrint)
23 changes: 21 additions & 2 deletions Manual/RecursiveDefs.lean
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Manual.Meta

import Manual.RecursiveDefs.Structural
import Manual.RecursiveDefs.WF
import Manual.RecursiveDefs.PartialFixpoint

open Verso.Genre Manual
open Lean.Elab.Tactic.GuardMsgs.WhitespaceMode
Expand All @@ -30,7 +31,7 @@ Furthermore, most useful recursive functions do not threaten soundness, and infi
Instead of banning recursive functions, Lean requires that each recursive function is defined safely.
While elaborating recursive definitions, the Lean elaborator also produces a justification that the function being defined is safe.{margin}[The section on {ref "elaboration-results"}[the elaborator's output] in the overview of elaboration contextualizes the elaboration of recursive definitions in the overall context of the elaborator.]

There are four main kinds of recursive functions that can be defined:
There are five main kinds of recursive functions that can be defined:

: Structurally recursive functions

Expand All @@ -48,6 +49,17 @@ There are four main kinds of recursive functions that can be defined:
Applications of functions defined via well-founded recursion are not necessarily definitionally equal to their return values, but this equality can be proved as a proposition.
Even when definitional equalities exist, these functions are frequently slow to compute with because they require reducing proof terms that are often very large.

: Recursive functions as partial fixpoints

The definition of a function can be understood as an equation that specifies its behavior.
In certain cases, the existence of a function that satisfies this specification can be proven even when the recursive function does not necessarily terminate for all inputs.
This strategy is even applicable in some cases where the function definition does not necessarily terminate for all inputs.
These partial functions emerge as fixed points of these equations are called {tech}_partial fixpoints_.

In particular, any function whose return type is in certain monads (e.g. {name}`Option`) can be defined using this strategy.
Lean generates additional partial correctness theorems for these monadic functions.
As with well-founded recursion, applications of functions defined as partial fixpoints are not definitionally equal to their return values, but Lean generates theorems that propositionally equate the function to its unfolding and to the reduction behavior specified in its definition.

: Partial functions with nonempty ranges

For many applications, it's not important to reason about the implementation of certain functions.
Expand All @@ -66,6 +78,11 @@ There are four main kinds of recursive functions that can be defined:
The replaced function may be opaque, which results in the function name having a trivial equational theory in the logic, or it may be an ordinary function, in which case the function is used in the logic.
Use this feature with care: logical soundness is not at risk, but the behavior of programs written in Lean may diverge from their verified logical models if the unsafe implementation is incorrect.

:::TODO

Table providing an overview of all strategies and their properties

:::

As described in the {ref "elaboration-results"}[overview of the elaborator's output], elaboration of recursive functions proceeds in two phases:
1. The definition is elaborated as if Lean's core type theory had recursive definitions.
Expand All @@ -78,7 +95,7 @@ As described in the {ref "elaboration-results"}[overview of the elaborator's out
If there is no such clause, then the elaborator performs a search, testing each parameter to the function as a candidate for structural recursion, and attempting to find a measure with a well-founded relation that decreases at each recursive call.

This section describes the rules that govern recursive functions.
After a description of mutual recursion, each of the four kinds of recursive definitions is specified, along with the tradeoffs between reasoning power and flexibility that go along with each.
After a description of mutual recursion, each of the five kinds of recursive definitions is specified, along with the tradeoffs between reasoning power and flexibility that go along with each.

# Mutual Recursion
%%%
Expand Down Expand Up @@ -156,6 +173,8 @@ After the first step of elaboration, in which definitions are still recursive, a

{include 0 Manual.RecursiveDefs.WF}

{include 0 Manual.RecursiveDefs.PartialFixpoint}

# Partial and Unsafe Recursive Definitions
%%%
tag := "partial-unsafe"
Expand Down
Loading