Skip to content

Add "if <term> is <pattern> then _ else _" syntax#21609

Draft
proux01 wants to merge 2 commits intorocq-prover:masterfrom
proux01:if-is
Draft

Add "if <term> is <pattern> then _ else _" syntax#21609
proux01 wants to merge 2 commits intorocq-prover:masterfrom
proux01:if-is

Conversation

@proux01
Copy link
Contributor

@proux01 proux01 commented Feb 10, 2026

New syntactic sugar if g is c then t else e and if g isn't c then t else e for match g with c => t | _ => e end and match g with c => e | _ => t end respectively. This adds the new reserved keywords is and isn't

Split out of #21478

In the long run (not this PR) we should maybe deprecate the specific if ... then ... else for inductives with two constructors (that is error prone, since it depends from the order of the constructors) with a quickfix toward the new if g is first_constructor then ... else variant and restrict if ... then ... else to booleans (or anything coercible to bool)

  • Added / updated test-suite.
  • Added changelog.
  • Added / updated documentation.
    • Documented any new / changed user messages.
    • Updated documented syntax by running make doc_gram_rsts.

Overlays (to be merged before the current PR)

Alternative

In the discussion below, some alternative syntax proposition came out. Respective advantages:

  • if <term> is <pattern> then <term> else <term> and if <term> isn't <pattern> then <term> else <term>
    • actually looks like a match (other solution looks more like a let-in)
    • in the spirit of the remaining ML-like syntax
    • space efficient (other solution wastes 50% of the gain compared to a match)
    • well tested for decades in many projects by many users (ssreflect users)
    • the isn't alternative offers a consistent solution for the, not uncommon, case when the main branch is the else branch (easily happens with nat or list for instance)
  • if let <pattern> := <term> then <term> else <term> and let <pattern> := <term> else <term> in <term>
    • doesn't require the new is keyword
    • similar to Rust (but it's a C-like syntax)
    • feels "easier to read and write" to a few users

@coqbot-app coqbot-app bot added the needs: full CI The latest GitLab pipeline that ran was a light CI. Say "@coqbot run full ci" to get a full CI. label Feb 10, 2026
@proux01 proux01 added the request: full CI Use this label when you want your next push to trigger a full CI. label Feb 10, 2026
@proux01 proux01 added this to the 9.3+rc1 milestone Feb 10, 2026
@coqbot-app coqbot-app bot removed request: full CI Use this label when you want your next push to trigger a full CI. needs: full CI The latest GitLab pipeline that ran was a light CI. Say "@coqbot run full ci" to get a full CI. labels Feb 10, 2026
@proux01 proux01 added the request: full CI Use this label when you want your next push to trigger a full CI. label Feb 10, 2026
@coqbot-app coqbot-app bot removed the request: full CI Use this label when you want your next push to trigger a full CI. label Feb 10, 2026
@proux01 proux01 added kind: enhancement Enhancement to an existing user-facing feature, tactic, etc. part: gallina The gallina commands labels Feb 10, 2026
@SkySkimmer
Copy link
Contributor

This adds the new reserved keywords is and isn't

Not sure we want this.
We could have something like if let pattern := v then e1 else e2 instead of if v is pattern then e1 else e2?
It would mean if let x := e in e then e else e stops parsing, but can still be parenthesized if (let x := e in e) then e else e (would need to change printing to use those parens).
Or use a new keyword if-let which probably isn't as disruptive?
Or ' like in let patterns and binders, ie if 'pattern := v then e1 else e2?

Not sure about isn't but do we actually need it?

@proux01
Copy link
Contributor Author

proux01 commented Feb 10, 2026

We could have something like if let pattern := v then e1 else e2 instead of if v is pattern then e1 else e2

I don't like that, it reverses the term and the pattern, making it look different than the match-with usual syntax.

Not sure about isn't but do we actually need it?

Yes, for instance for the common idiom if l isn't cons x l' then <trivial thing> else <longer actual code using x and l'>. Sure you could write if l is cons x l' then <longer actual code using x and l'> else <trivial thing> but you definitely don't want to.

@yannl35133
Copy link
Contributor

yannl35133 commented Feb 10, 2026

isn't could be "is"; IDENT "not", could it not?
EDIT: unless someone names a constructor "not"...

@SkySkimmer
Copy link
Contributor

if e then 'pat => e else 'pat => e? not very pretty I guess (if e0 then e1 else 'pat => e2 would reorder branches to desugar to match e0 with pat => e2 | _ => e1 end)

@TheoWinterhalter
Copy link
Contributor

I would say that the isn't version is very misleading, I wouldn't expect it to perform matching in the else branch.

@proux01
Copy link
Contributor Author

proux01 commented Feb 10, 2026

isn't could be "is"; IDENT "not", could it not?

Would avoid making ins't a keyword but not sure it's worth it.

if e then 'pat => e else 'pat => e? not very pretty I guess

Well, at this point we can just write the match.

I would say that the isn't version is very misleading, I wouldn't expect it to perform matching in the else branch.

If you write things like if _ isn't _ then <very long term> else _ probably, but that's nothing specific to isn't. The common use case if rather if _ isn't <pattern with vars> then <short term obviously without vars> else <term with vars>

Don't forget that we have more than a decade of experience with the syntax in the entire mathcomp ecosystem and that it seems to work pretty well (at least in that context).

@TheoWinterhalter
Copy link
Contributor

TheoWinterhalter commented Feb 10, 2026

My understanding is that the syntax of mathcomp is the main barrier for adoption.

Which is not to mean that it's an argument to reject the notation, but just "trust me" seems a bit weak as argument for it. But I'm not part of the Rocq team so I will not fight against it of course.

Just to clarify I'm not trying to be mean, I just wanted to express my concerns and it's very likely misplaced.

@andres-erbsen
Copy link
Contributor

I find the Rust if let syntax easier to read and write despite learning about the mathcomp syntax earlier having read more code that uses it.

@proux01
Copy link
Contributor Author

proux01 commented Feb 11, 2026

"trust me" seems a bit weak as argument for it

The argument is not "trust me", rather: it has been used extensively, by many people for a long time, and proved effective (at least in that context, maybe more math oriented than PL community). You can browse all the codebase by yourself if you want to check.

Just to clarify I'm not trying to be mean, I just wanted to express my concerns and it's very likely misplaced.

No worries, any opinion is welcome, as long as it remain based on concrete arguments and facts.

I find the Rust if let syntax easier to read and write

I can indeed see that syntax fitting better in the C-like syntax of Rust (where "if" may be the primary control structure, no "then" keyword, no "in" keyword for "let",...) but it doesn't fit well in the ML like syntax of Rocq (with "match" being the main control structure, with "with" and "then" keywords, "let" always followed by "in",...). As already pointed above, such syntaxes with "let" reverse the order of the term and the pattern compared to the usual "match term with (| pattern => term)* end" syntax.

@SkySkimmer
Copy link
Contributor

As already pointed above, such syntaxes with "let" reverse the order of the term and the pattern compared to the usual "match term

I don't think that's really a problem, we already have let 'pattern := e in e

@proux01
Copy link
Contributor Author

proux01 commented Feb 12, 2026

That's my point, the pattern in let is only doing some destructuring, no control flow, since a let is a match with a single branch. In contrary, an if is essentially a match with multiple branches.

The case where the else branch is the main one (not uncommon, think about nat or list for instance) also remains unclear in that kind of proposals.

Anyway, I tried to sum up the discussion in the top message, feel free to point any argument I would have forgotten.

@TheoWinterhalter
Copy link
Contributor

Another alternative could be to change if rather than the is. For instance

unless t is p then _ else _

The main advantage I see is that the pattern matching clause is still framed positively, so it restores, in my mind, the idea that some new variables are bound.

In any case, doesn't this syntax encourage bad matching like the following?

if n is 3 then _ else _

To me it now feels more natural to write, but you should actually not do it. You can make things worse by using a string rather than 3 in the above example.

@andres-erbsen
Copy link
Contributor

andres-erbsen commented Feb 12, 2026

I agree with @proux01 that the large-else-branch scenario is important and could benefit from nice shorthand syntax. (Thank you for stressing it, I had been thinking about the if .. is construct as the main feature until then.)

Here are some more options that come to mind that I would hope to be broadly recognizable:

let 'pattern := ex else default in eC (rust)

match ex with pattern else default in eC

and

let 'pattern := ex then eC else default

match ex with pattern => eC else default

To be confirmed whether they're compatible with the grammar, though.

@samuelgruetter
Copy link
Contributor

Indeed, a construct that allowed me to write short error handling branches before a main branch that does not get formatted at a higher level of indentation, like eg the following, seems useful:

let 'x::xs := myList else None in
some lengthy code referring to x and xs;
formatted without an additional level of indentation;
finally returning
Some result

Or, if I understood correctly, the same using isn't would be

if myList isn't x::xs then None else
some lengthy code referring to x and xs;
formatted without an additional level of indentation;
finally returning
Some result

@proux01
Copy link
Contributor Author

proux01 commented Feb 16, 2026

@samuelgruetter added to the if-let offer in the top message

@proux01
Copy link
Contributor Author

proux01 commented Feb 20, 2026

To be discussed in a future Call: https://github.com/rocq-prover/rocq/wiki/Rocq-Call-2026-03-17

@SkySkimmer
Copy link
Contributor

BTW we already have

Notation "'if' c 'is' p 'then' u 'else' v" :=

@github-actions github-actions bot added the needs: rebase Should be rebased on the latest master to solve conflicts or have a newer CI run. label Feb 26, 2026
@proux01
Copy link
Contributor Author

proux01 commented Feb 26, 2026

BTW we already have

I know, note that there is a small difference in the handling of shallow scope opening (which goes through each branches of the native match but not through the notation)

@andres-erbsen
Copy link
Contributor

Lean seems to support let y <- f x | z (search, elaborator)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind: enhancement Enhancement to an existing user-facing feature, tactic, etc. needs: rebase Should be rebased on the latest master to solve conflicts or have a newer CI run. part: gallina The gallina commands

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants