Zulip Chat Archive
Stream: lean4
Topic: termination of syntax consumption
Arthur Paulino (Apr 01 2022 at 11:51):
Hello everyone!
Something got me thinking: is there a way to infer the termination of Syntax → ...
functions when every recursive call uses a fragment of the entire syntax we're matching on?
I'm asking this because a piece of code that I'm writing has multiple partial
definitions that I believe should always terminate. And this is not the first time that I end up with many partial functions like this
Henrik Böving (Apr 01 2022 at 11:57):
I'm definitely not an expert on Syntax
but I think the issue with this might be the following: Syntax
itself is actually a very trivial datatype: docs4#Lean.Syntax however the notation we have allows to match on fairly elaborate (elaborate to denote with a pattern on Syntax
) Syntax tree nodes so maybe the information about being a "subtree" just gets lost along the way?
Mario Carneiro (Apr 01 2022 at 11:57):
The way syntax matches are currently desugared, it would not be easy. You can use a well founded measure to determine that the array accesses are at smaller members, but it doesn't seem like it is worth complicating the desugaring
Henrik Böving (Apr 01 2022 at 12:01):
Regarding this topic, I also have a similar question regarding Expr
transformers, I wrote this function here:
https://github.com/hargoniX/mathlib4/blob/slim_check/Mathlib/Testing/SlimCheck/Testable.lean#L463-L472
but due to recursing into addDecorations
again inside the argument to replace
It cannot be trivially shown to terminate, do we have tricks to do this as well ?
Mario Carneiro (Apr 01 2022 at 14:13):
nope, that one is even harder because changing the type of replace
would interfere with the (very important!) caching optimization
Leonardo de Moura (Apr 03 2022 at 02:21):
The test file https://github.com/leanprover/lean4/blob/master/tests/lean/run/syntaxWF.lean contains the necessary "plumbing" for writing functions such as
def visit (stx : Syntax) : Syntax :=
match stx with
| `(if $c then $t else $e) => visit c
| `(let $x:ident := $v:term; if $c then $t else $e) => visit c
| _ => stx
without partial
.
For the addDecorations
example, we can define a refine'
function with type
def replace' (e0 : Expr) (f? : (e : Expr) → sizeOf e ≤ sizeOf e0 → Option Expr) : Expr :=
It is the same trick we use for other combinators. Then, we can write
def addDecorations (e : Expr) : Expr :=
e.replace' fun expr h =>
match expr with
| Expr.forallE name type body data =>
let n := name.toString
let newType := addDecorations type
let newBody := addDecorations body
let rest := Expr.forallE name newType newBody data
some <| mkApp2 (mkConst `SlimCheck.NamedBinder) (mkStrLit n) rest
| _ => none
decreasing_by exact Nat.le_trans (by simp_arith) h
The complete example is here: https://github.com/leanprover/lean4/blob/master/tests/lean/run/addDecorationsWithoutPartial.lean
We are saving all these examples in our test suite because we want all of them to go through without any extra annotation even if the user uses Expr.replace
. Our goal is to allow users to register replacements such as Expr.replace
=> Expr.replace'
that are applied when we try to prove termination. We have some notes here:
https://github.com/leanprover/lean4/blob/master/tests/lean/run/combinatorsAndWF.lean#L37
Arthur Paulino (Apr 03 2022 at 05:15):
Starred the message so I can come back to it later :+1:
I forgot to mention another motivation for my question. Sebastian mentioned the builtin syntax
which will help us build powerful parsers without the gymnastics that were implemented in my repo. Then, if writing recursive functions that consume syntax terms is easier and possible without partial
, then we'll be able to reason about them more easily
Last updated: Dec 20 2023 at 11:08 UTC