Zulip Chat Archive

Stream: lean4

Topic: Proving termination of monadic functions


Henrik Böving (Sep 16 2022 at 14:11):

Given a monadic function where the thing that is reducing size such as a Parsec one, how can I tell the termination_by that I am actually interested in proving that the size of the monadic state is decreasing, e.g.:

import Lean
open Lean Parsec

inductive Exp where
| num (x : Int)
| add (lhs : Exp) (rhs : Exp)

def parseNum : Parsec Exp := do
  let digits := String.mk (many1 digit).toList
  return .num (String.toInt! digits)

def isAtEof? : Parsec Bool := fun it => .success it it.atEnd

def parseAdd (lhs : Exp) : Parsec Exp := do
  if (isAtEof?) then
    return lhs
  else
    ws -- whitespace
    skipChar '+'
    ws
    let rhs  parseNum
    let newLhs := .add lhs rhs
    parseAdd newLhs
termination_by <what do I do here>


def parser : Parsec Exp := do
  let initialLhs  parseNum
  parseAdd initialLhs

I cannot termination_by parsedAdd lhs it => because I can only used the named arguments here it seems so how can I access it?

James Gallicchio (Sep 16 2022 at 17:14):

I'm not sure I understand completely, what should it be ?

Can you add a fuel parameter to parseAdd based on the size of the state?

Henrik Böving (Sep 16 2022 at 18:15):

it is the String.Iterator that Parsec is keeping as its internal state, as you can see in the isAtEof? implementation it is the implicit parameter passed in by the Parsec monad so I would like to be able to refer to it from within termination_by as well.

I dont exactly see how the fuel parameter stuff would work, could you show an example? The code above is fully self contained.

Jannis Limperg (Sep 16 2022 at 18:25):

You could always unfold Parsec in the type signature and explicitly introduce it. Then you could try to abstract the termination argument into a combinator since you'll be using it a lot.

James Gallicchio (Sep 16 2022 at 19:03):

Something like this:

def parseAdd' (lhs : Exp) (fuel : Nat) : Parsec Exp := do
  if (isAtEof?) then
    return lhs
  else
    ws -- whitespace
    skipChar '+'
    ws
    let rhs  parseNum
    let newLhs := .add lhs rhs
    let newFuel := sorry
    have : newFuel < fuel := sorry
    parseAdd' newLhs newFuel
termination_by _ fuel => fuel

def parseAdd (lhs : Exp) : Parsec Exp :=
  λ it => parseAdd' lhs (sorry /- enough fuel -/) it

James Gallicchio (Sep 16 2022 at 19:05):

(You'll probably need to require something about fuel to prove you can have a newFuel always, but hopefully your size measure is simple?)


Last updated: Dec 20 2023 at 11:08 UTC