Zulip Chat Archive

Stream: lean4

Topic: Improving this syntax


François G. Dorais (Mar 23 2022 at 09:43):

After another mostly unrelated discussion, I remembered that I had been using a custom by_cases tactic. Here is my current code:

syntax by_cases_true := "|" &"isTrue" (ident <|> "_") "=>" (hole <|> syntheticHole <|> tacticSeq)
syntax by_cases_false := "|" &"isFalse" (ident <|> "_") "=>" (hole <|> syntheticHole <|> tacticSeq)
syntax "by_cases" term "with" withPosition((colGe by_cases_true) (colGe by_cases_false)) : tactic
macro_rules
| `(tactic|by_cases $p with | isTrue $ht => $tt | isFalse $hf => $tf) =>
  `(tactic|match inferInstanceAs (Decidable $p) with | Decidable.isTrue $ht => $tt | Decidable.isFalse $hf => $tf)

My main idea was to use a match-style syntax to allow me to use different identifiers in the true/false cases, whereas the built-in by_cases forces the same identifier in both cases. (As an unintended bonus, this version of by_cases actually checks whether the proposition is decidable and the built-in version does not.)

There is an obvious defect to this code where I'm forced to write the cases in the true then false order. I think I know how to fix that. But what I really want and couldn't find a way to make it work is to allow by_cases to apply to several propositions. For example, I would want to write:

by_cases p, q, r with
| ifTrue hp, ifTrue hq, ifTrue hr => ...
| ifTrue _, ifTrue hq, ifFalse hnr => ...
| ifTrue _, ifFalse hnq, ifTrue _ => ...
| ifTrue hp, ifFalse hnq, ifFalse _ => ...
| ifFalse hnp, ifTrue hq, ifTrue hr => ...
| ifFalse hnp, ifTrue hq, ifFalse hnr => ...
| ifFalse hnp, ifFalse _, ifTrue hr => ...
| ifFalse hnp, ifFalse hnq, ifFalse hnr => ...

I gave up after a couple of headaches. Maybe someone with more expertise knows how to do this? (Bonus points if the cases can be in any order.)

Jannis Limperg (Mar 23 2022 at 10:18):

I would try to do this one syntactically: transform

by_cases x, y, ... with

=>

match decide x, decide y, ... with

and use the regular syntax for match alternatives for the cases.

Mario Carneiro (Mar 23 2022 at 10:31):

macro (name := byCases) "by_cases" ps:term,* "with" alts:Lean.Parser.Tactic.matchAlts : tactic =>
  `(tactic| match $[inferInstanceAs (Decidable $ps)],* with $alts:matchAlts)

example : True := by
  by_cases 1 < 2, 3 < 4 with
  | isTrue h1, isTrue h2 => trivial
  | isTrue h1, isFalse h2 => trivial
  | isFalse h1, isTrue h2 => trivial
  | isFalse h1, isFalse h2 => trivial

François G. Dorais (Mar 23 2022 at 12:32):

You rock Mario!

It looks simpler but I don't think your idea works Jannis; how do I get case-by-case identifiers that way?

François G. Dorais (Mar 23 2022 at 12:35):

I guess $[...] is way more powerful than I thought. Does it do some kind of automatic folding? I don't really get how $[inferInstanceAs (Decidable $ps)],* actually works. I'll have to mull it over for a bit...

Jannis Limperg (Mar 23 2022 at 12:44):

François G. Dorais said:

It looks simpler but I don't think your idea works Jannis; how do I get case-by-case identifiers that way?

I meant exactly what Mario did, only with more fuzziness. ;)

Mario Carneiro (Mar 23 2022 at 12:54):

François G. Dorais said:

I guess $[...] is way more powerful than I thought. Does it do some kind of automatic folding? I don't really get how $[inferInstanceAs (Decidable $ps)],* actually works. I'll have to mull it over for a bit...

You can look at the generated code for syntax quotations by doing stuff like

open Lean Syntax in
#check fun ps alts => `(tactic| match $[inferInstanceAs (Decidable $ps)],* with $alts:matchAlts)
fun ps alts => do
  let info  MonadRef.mkInfoFromRefPos
  let scp  getCurrMacroScope
  let mainModule  getMainModule
  pure
      (node SourceInfo.none `Lean.Parser.Tactic.match
        #[atom info "match", node SourceInfo.none `null #[], node SourceInfo.none `null #[],
          node SourceInfo.none `null
            (Array.append #[]
              (mkSepArray
                (Array.map -- map here
                  (fun ps =>
                    node SourceInfo.none `Lean.Parser.Term.matchDiscr
                      #[node SourceInfo.none `null #[],
                        node SourceInfo.none `Lean.Parser.Term.app
                          #[ident info (String.toSubstring "inferInstanceAs")
                              (addMacroScope mainModule `inferInstanceAs scp) [(`inferInstanceAs, [])],
                            node SourceInfo.none `null
                              #[node SourceInfo.none `Lean.Parser.Term.paren
                                  #[atom info "(",
                                    node SourceInfo.none `null
                                      #[node SourceInfo.none `Lean.Parser.Term.app
                                          #[ident info (String.toSubstring "Decidable")
                                              (addMacroScope mainModule `Decidable scp) [(`Decidable, [])],
                                            node SourceInfo.none `null #[ps]], --insert ps into the tree
                                        node SourceInfo.none `null #[]],
                                    atom info ")"]]]])
                  ps) -- the actual `Array Syntax` input
                (mkAtom ","))),
          atom info "with", alts]) : (ps : Array Syntax)  (alts : Syntax)  ?m.1001 ps alts Syntax

You have to squint a bit to see what it's doing, but you can see the Array.map in there

Yuri de Wit (Mar 23 2022 at 14:14):

TIL: #check ...


Last updated: Dec 20 2023 at 11:08 UTC