Zulip Chat Archive

Stream: general

Topic: alternative to nesting of if..then..else


Alexandre Rademaker (Jul 19 2024 at 15:31):

def appendSuffixF : String  String  String
  | stem, suffix => stem ++ suffix.map (vh stem)
 where
  vh s :=
   if s.any (·  "aou".toList) then back
   else if s.any (·  [aUml, oUml, 'y']) then front else id

In the vh local definition above, I would like to avoid the if ... then ... else if ... then ... else .... The guards from Haskell would be perfect here. Any alternative?

Do we have any debug option to measure the time to evaluate a function? Like

#eval time EXP

Edward van de Meent (Jul 19 2024 at 15:32):

is it the syntax you dislike, or the resulting definition?

Alexandre Rademaker (Jul 19 2024 at 15:33):

The syntax, the function is working as expected.

Edward van de Meent (Jul 19 2024 at 15:36):

the reason i ask is because i don't know of an alternative definition, however i do know that lean has a nifty feature where you can define your own syntax

Edward van de Meent (Jul 19 2024 at 15:36):

you might be particularly interested in the book metaprogramming in lean 4

Edward van de Meent (Jul 19 2024 at 15:36):

(deleted)

Alexandre Rademaker (Jul 19 2024 at 15:41):

Yes, definitely, this book is on my list. Thanks.

Kim Morrison (Jul 19 2024 at 23:12):

if is pretty standard in Lean code, and probably preferable to using Haskell like "guard"s. We have tactic infrastructure (split in Lean, and split_ifs in Mathlib) that handles it well.

Kyle Miller (Jul 19 2024 at 23:19):

Lean has special support for else if in the whitespace-sensitive syntax for you to be able to write

def appendSuffixF : String  String  String
  | stem, suffix => stem ++ suffix.map (vh stem)
 where
  vh s :=
   if s.any (·  "aou".toList) then
     back
   else if s.any (·  [aUml, oUml, 'y']) then
     front
   else
     id

Sometimes you also see

def appendSuffixF : String  String  String
  | stem, suffix => stem ++ suffix.map (vh stem)
 where
  vh s :=
   if s.any (·  "aou".toList) then back
   else if s.any (·  [aUml, oUml, 'y']) then front
   else id

But you don't need the whole sub-if statement nested under the else

Kyle Miller (Jul 19 2024 at 23:20):

I'm not sure this is the only issue with it, but lean4#371 mentions a request for pattern guards.

Kyle Miller (Jul 19 2024 at 23:25):

(I was going to give some other variants, but I'm confused about this function since it doesn't type check...

Anyway, I think this looks better, but I can't check it:

def appendSuffixF (stem suffix : String) : String :=
  stem ++ suffix.map vh
where
  vh :=
    if stem.any (·  ['a', 'o', 'u']) then back
    else if stem.any (·  ['ä', 'ö', 'y']) then front
    else id

)

Paul Chisholm (Jul 20 2024 at 06:54):

I like Lean' s compact syntax but find chained if-then-else to be a bit awkward. I always liked Dijkstra's Guarded Command Language (GCL), it has a nice visual balance, which led to this definition:

syntax " if " (term " ⟹ " term " ‖ ")+ " ⟹ " term " fi " : term

macro_rules
  | `(if $c₁  $e₁   $e₂ fi) =>
    `(ite $c₁ $e₁ $e₂)
  | `(if $c₁  $e₁  $[$cs₁  $es₁ ]*  $e₂ fi) =>
    `(ite $c₁ $e₁ (if $[$cs₁  $es₁ ]*  $e₂ fi))

def test (n : Nat) : String :=
    if n == 0  "zero"
      n == 1  "one"
      n == 2  "two"
      n == 3  "three"
              "> 3"
    fi

#eval test 2
#eval test 4

def appendSuffixF (stem suffix : String) : String :=
  stem ++ suffix.map vh
where
  vh :=
    if stem.any (·  ['a', 'o', 'u'])  id
      stem.any (·  ['ä', 'ö', 'y'])  id
                                      id
    fi

Unlike GCL, the definition is deterministic, and requires a catchall.

Note: I replaced back and front in the definition of appendSuffixF to allow it to compile.

James Sully (Jul 20 2024 at 12:16):

Kyle Miller said:

I'm not sure this is the only issue with it, but lean4#371 mentions a request for pattern guards.

This seems to be for or-patterns, which aren't quite the same thing

James Sully (Jul 20 2024 at 12:17):

although they are mentioned in the issue discussion

Edward van de Meent (Jul 20 2024 at 12:20):

Paul Chisholm said:

which led to this definition:

is there a(n easy) way to amend this definition to also allow for dite?

Kyle Miller (Jul 20 2024 at 15:55):

@Paul Chisholm Interesting; a version of that that would fit right in as Lean syntax would be a Lisp-style cond using match syntax:

syntax condAlt := "| " ppIndent(term)? Lean.Parser.darrow colGe term
syntax "cond " many1(ppLine condAlt) : term

macro_rules
  | `(cond $[| $(cs)? => $as]* ) => show MacroM Term from do
    if as.isEmpty then Macro.throwUnsupported
    let t  `(true)
    let cs := cs.map fun c => c.getD t
    unless cs.back == t do Macro.throwError "Last alternative must be unconditional"
    (cs.pop.zip as.pop).foldrM (init := as.back) fun (c, a) e =>
      `(if $c then $a else $e)

def test (n : Nat) : String :=
  cond
  | n == 0 => "zero"
  | n == 1 => "one"
  | n == 2 => "two"
  | n == 3 => "three"
  |        => "> 3"

Kyle Miller (Jul 20 2024 at 16:06):

@Edward van de Meent Yes:

import Lean

open Lean

syntax condAlt := "| " ppIndent(atomic(binderIdent " : ")? term)? Parser.darrow colGe term
syntax "cond " many1(ppLine condAlt) : term

macro_rules
  | `(cond $[| $[$[$bis:binderIdent :]? $cs:term]? => $as]* ) => show MacroM Term from do
    if as.isEmpty then Macro.throwUnsupported
    unless cs.back.isNone do Macro.throwError "Last alternative must be unconditional"
    let cs  cs.mapM fun c => c.getDM `(true)
    unless bis.back.join.isNone do Macro.throwError "Last alternative must not have a proof binder"
    (bis.pop.zip (cs.pop.zip as.pop)).foldrM (init := as.back) fun (bi, c, a) e =>
      if let some bi := bi.join then
        `(if $bi : $c then $a else $e)
      else
        `(if $c then $a else $e)

def test (n : Nat) : String :=
  cond
  | n == 0 => "zero"
  | n == 1 => "one"
  | n == 2 => "two"
  | n == 3 => "three"
  |        => "> 3"

example (n : Nat) (a : Array Nat) : Nat :=
  cond
  | h : n < a.size => a[n]
  | => 0

Kyle Miller (Jul 20 2024 at 16:07):

Maybe _ could be allowed for the unconditional case.

Edward van de Meent (Jul 20 2024 at 16:07):

what are the imports?

Kyle Miller (Jul 20 2024 at 16:10):

@Edward van de Meent I just updated the last code block

Paul Chisholm (Jul 20 2024 at 23:18):

@Kyle Miller Nice alternative and, as you say, more consistent with existing Lean syntax.

Alexandre Rademaker (Jul 21 2024 at 15:37):

Thank you all, nice syntax extension. It will be cool to show the students how Lean language can be extended and a concrete example when this can make sense.

Mac Malone (Jul 28 2024 at 17:18):

Note that this syntax can reuse the if keyword instead of reserving a new one (since the syntaxes are non-overlapping):

import Lean.Parser

open Lean

syntax ifAlt := "| " ppIndent(atomic(binderIdent " : ")? term)? Parser.darrow colGe term
syntax "if " many1(ppLine ifAlt) : term

macro_rules
  | `(if $[| $[$[$bis:binderIdent :]? $cs:term]? => $as]* ) => show MacroM Term from do
    if as.isEmpty then Macro.throwUnsupported
    unless cs.back.isNone do Macro.throwError "Last alternative must be unconditional"
    let cs  cs.mapM fun c => c.getDM `(true)
    unless bis.back.join.isNone do Macro.throwError "Last alternative must not have a proof binder"
    (bis.pop.zip (cs.pop.zip as.pop)).foldrM (init := as.back) fun (bi, c, a) e =>
      if let some bi := bi.join then
        `(if $bi : $c then $a else $e)
      else
        `(if $c then $a else $e)

def test (n : Nat) : String :=
  if
  | n == 0 => "zero"
  | n == 1 => "one"
  | n == 2 => "two"
  | n == 3 => "three"
  |        => "> 3"

example (n : Nat) (a : Array Nat) : Nat :=
  if
  | h : n < a.size => a[n]
  | => 0

Yury G. Kudryashov (Jul 28 2024 at 20:05):

Should we add this syntax to (a dependency of) Mathlib?


Last updated: May 02 2025 at 03:31 UTC