Zulip Chat Archive
Stream: new members
Topic: Equivalent of "Program Definition" in Coq
Youngju Song (Dec 17 2024 at 01:37):
Let us say I have the following code:
def myfunc (xs ys: List Nat): IO (List Nat) := do
assert!(xs.length = ys.length)
let mut res := nil
for i in [0:xs.length] do
res := (xs[i]! + ys[i]!) :: res
return res
and I want to replace xs[i]!
with xs[i]
by providing proper proofs, thereby reducing the possibility of runtime failure.
In Coq, I could try using "Program Definition" keyword, where you can leave the proofs as blanks _
and later provide the proof once the definition is over. Is there similar functionality in Lean4?
nrs (Dec 17 2024 at 01:53):
You may use the partial def
keyword to ignore termination proofs and sorry
to provide placeholders for proofs
nrs (Dec 17 2024 at 01:54):
If you meant tactics that create goals according to how many holes you have in your proof, you might be interested in refine
Matt Diamond (Dec 17 2024 at 01:54):
you can also add the proofs as have
assumptions within the function... that will suffice for xs[i]
to work
Matt Diamond (Dec 17 2024 at 01:55):
import Mathlib
def myfunc (xs ys: List Nat): IO (List Nat) := do
assert!(xs.length = ys.length)
let mut res := []
for i in [0:xs.length] do
have : i < xs.length := sorry
have : i < ys.length := sorry
res := (xs[i] + ys[i]) :: res
return res
Matt Diamond (Dec 17 2024 at 01:57):
I'm intrigued by the use of assert!
though... it would be cool if that added proof assumptions, but it doesn't appear to
Matt Diamond (Dec 17 2024 at 02:01):
if it did then you could do something neat like this:
import Mathlib
def myfunc (xs ys: List Nat): IO (List Nat) := do
assert!(xs.length = ys.length)
let mut res := []
for i in List.finRange xs.length do
have : i < ys.length := my_assert_proof ▸ (Fin.is_lt _)
res := (xs[i] + ys[i]) :: res
return res
Matt Diamond (Dec 17 2024 at 02:07):
also it goes without saying that you could define the function so that the caller has to pass in a proof of xs.length = ys.length
Youngju Song (Dec 17 2024 at 02:09):
@nrs @Matt Diamond Thank you for the reply!
What Matt wrote is close to what I want. Coq's "Program Definition" does that for the user. It would be ideal if something like the following works, which uses xs[i]'h
notation for providing the proof h
, but it is left as a blank in the definition time and gets resolved separately (so that it doesn't corrupt the readability of the definition) in the Next Obligation
block below.
def myfunc (xs ys: List Nat): IO (List Nat) := do
assert!(xs.length = ys.length)
let mut res := []
for i in [0:xs.length] do
res := (xs[i]'_ + ys[i]'_) :: res
return res
Next Obligation.
-- proof obligation for the first occurrence of `_`.
Qed.
Next Obligation.
-- proof obligation for the second occurrence of `_`.
Qed.
Btw, what is the ▸
notation? Is it the same as |>
?
Matt Diamond (Dec 17 2024 at 02:11):
▸
is a macro built on top of Eq.rec
and Eq.symm
... it's just a shorthand for rewriting based on an equality
Matt Diamond (Dec 17 2024 at 02:12):
so in my example I was saying that if we had a proof of xs.length = ys.length
we could use it to rewrite i < xs.length
to i < ys.length
nrs (Dec 17 2024 at 02:13):
If the obligations are of significant size, it is in my experience idiomatic in Lean to create new theorem
or lemma
declarations that specifically provide these proofs (or that provide the statement of the proofs followed by := sorry
). If the proof obligations are small, you may explicitly write metavariables ?myVar
, ?foo
in place of the holes, and this will make them appear as new goals. The refine
tactic is a small variation of this workflow where all the holes can be placed in a single function, or you can use Matt's suggested have
keywords. suffices
is also worth looking into for managing these workflows
Youngju Song (Dec 17 2024 at 21:48):
@Matt Diamond Thank you for the explanation!
Youngju Song (Dec 17 2024 at 21:51):
@nrs Thank you for the reply! I am not sure I fully follow your explanation though. (1) Does "sorry" allow one to provide the proof latter? What would be the syntax for doing this? (2) I tried to use metavariables as you suggested, but not sure how to make it work - it gives me a syntax error (4.15.0-rc1). Would you mind providing a minimal code snippet that I can copy-paste and play with?
nrs (Dec 17 2024 at 22:07):
def myfunc (xs ys: List Nat): IO (List Nat) := do
assert!(xs.length = ys.length)
let mut res := .nil
for i in [0:xs.length] do
res := (xs[i]'sorry + ys[i]'sorry) :: res
return res
def myfunc' (xs ys: List Nat): IO (List Nat) := do
assert!(xs.length = ys.length)
let mut res := .nil
for i in [0:xs.length] do
res := (xs[i]'(p i) + ys[i]'(q i)) :: res
return res
where
p x := sorry
q x := sorry
nrs (Dec 17 2024 at 22:08):
I had not considered: the metavariables syntax, andrefine
and suffices
are only available in tactics mode! (which must begin with a tactic block starting with the termby
)
Kyle Miller (Dec 17 2024 at 23:08):
@Youngju Song Re (1), no sorry
does not allow you to provide the proof later.
Eric Wieser (Dec 17 2024 at 23:09):
Youngju Song said:
I tried to use metavariables as you suggested, but not sure how to make it work
def myfunc'' (xs ys: List Nat): IO (List Nat) := by
refine do
assert!(xs.length = ys.length)
let mut res := .nil
for i in [0:xs.length] do
res := (xs[i]'?_ + ys[i]'?_) :: res
return res
· sorry
· sorry
Kyle Miller (Dec 17 2024 at 23:11):
There's currently no "program definition"-style system, but it seems like it should be possible to make a prototype for something that would effectively do what Eric just wrote, but with some potentially nicer syntax and properties. (Using by
and refine
like this changes the way in which the function elaborates in some subtle ways, and it should be possible to avoid this.)
Kyle Miller (Dec 17 2024 at 23:12):
I could imagine something like
def myfunc (xs ys: List Nat): IO (List Nat) := do
assert!(xs.length = ys.length)
let mut res := nil
for i in [0:xs.length] do
res := (xs[i]'?h1 + ys[i]'?h2) :: res
return res
obligations
case h1 => -- ... proof ...
case h2 => -- ... proof ...
There is already a procedure to collect metavariables from a definition for error reporting purposes, but this obligations
block could be a way to capture them.
Matt Diamond (Dec 17 2024 at 23:13):
any thoughts on assert!
introducing assumptions? or is that a bad idea?
Matt Diamond (Dec 17 2024 at 23:14):
just asking if it would be theoretically desirable (I know it doesn't work that way right now)
nrs (Dec 17 2024 at 23:14):
we'd probably want that as an explicitly passed proof, since panic in Lean doesn't crash the program but produces a value
Kyle Miller (Dec 17 2024 at 23:15):
At least for now you can use an if
/else
to collect assumptions. I can't comment on having assert!
be able to introduce hypotheses.
Joachim Breitner (Dec 18 2024 at 09:25):
Funnily, assert!
does introduce assumptions for termination checking (because it’s using ite
, and when handling recursion, ite
is turned into dite
):
Marcus Rossel (Dec 18 2024 at 09:44):
(deleted)
Youngju Song (Dec 20 2024 at 15:53):
Thank you all for the advices!!
What @nrs and @Eric Wieser suggested seems to work well for my small examples.
@Kyle Miller what you suggested looks interesting. (1) Is there some feature request channel for Lean developers? (2) Do you think it could be implemented in the user side (without modifying Lean itself) with reasonable effort? If that is the case, maybe I can also give it a shot with advices from experts in the community.
Kyle Miller (Dec 20 2024 at 18:23):
@Youngju Song I got a prototype working:
syntax "program " term " by " Parser.Tactic.tacticSeqIndentGt : term
macro_rules
| `(program $t by%$byTk $seq:tacticSeq) =>
-- these %$_ annotations are a bid to get errors to appear in sensible places
withRef (mkNullNode #[byTk, seq]) `(by%$byTk refine%$t $t; ( $seq ))
def myfunc (xs ys : List Nat) : IO (List Nat) := program do
if hl : xs.length = ys.length then
let mut res := []
for h : i in [0:xs.length] do
res := (xs[i]'?h1 + ys[i]'?h2) :: res
return res
else
panic! "lengths not equal"
by
case h1 => exact h.2
case h2 => rw [← hl]; exact h.2
Kyle Miller (Dec 20 2024 at 18:24):
(The assert!
wasn't adding any hypothesis to the context that h1
or h2
could capture, so I changed it to an if
manually.)
Kyle Miller (Dec 20 2024 at 18:29):
(@Joachim Breitner What do you think about an obligations_by
clause for definitions?)
Joachim Breitner (Dec 21 2024 at 13:53):
Plausible, but I'm not immediately convinced it's worth the extra language surface complexity. Maybe some program verification projects would benefit? Maybe your prototype is goin enough for those users to scratch that itch and demonstrate the usefulness of this idiom?
Also does this need to be attached to definitions, or could it be a term syntax, similar to where
and like your prototype, so that it can be used locally as well, and everywhere where terms are expected, rather than just in definitions?
Mario Carneiro (Dec 21 2024 at 14:07):
note that program
above is a term syntax
Mario Carneiro (Dec 21 2024 at 14:08):
I feel like a design here should also allow doing things like coq-procrastination
Mario Carneiro (Dec 21 2024 at 14:09):
in particular, you should be able to generate a metavariable inside a block scope and solve it outside that scope, provided there are enough opt-in markers
Youngju Song (Jan 01 2025 at 01:32):
@Kyle Miller Wow, that looks much more elegant than what I thought, thank you for doing this! I am playing with it and so far it seems to work. I will report here if I find an issue.
Frédéric Dupuis (Jan 01 2025 at 03:15):
Regarding assert!
being able to introduce the hypothesis being asserted, the following works pretty well:
def checkThat (p : Prop) [Decidable p] : Option (PLift p) :=
if h : p then some ⟨h⟩ else none
def myfunc (xs ys: List Nat): IO (List Nat) := do
let some ⟨hlength⟩ := checkThat (xs.length = ys.length) | panic! "different lengths"
let mut res := []
for i in List.finRange xs.length do
res := (xs[i] + ys[i]) :: res
return res
Last updated: May 02 2025 at 03:31 UTC