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