Zulip Chat Archive
Stream: lean4
Topic: Undefine lambda
Sky Wilshaw (Sep 28 2023 at 10:31):
Is there a way to undefine the special function syntax for λ?
variable {λ : Type _} -- unexpected token 'λ'; expected '_' or identifier
Mario Carneiro (Sep 28 2023 at 11:52):
No, undefining tokens is not allowed. But let's do it anyway:
import Lean
open Lean Parser
def Lean.Parser.Trie.isEmpty {α} (t : Trie α) : Bool := t matches .Node none .leaf
partial def Lean.Parser.Trie.erase {α} (t : Trie α) (s : String) : Trie α :=
  let rec loop : Trie α → String.Pos → Option (Trie α)
    | ⟨val, m⟩, i =>
      match s.atEnd i with
      | true  => some (Trie.Node none m)
      | false => do
        let c := s.get i
        let i := s.next i
        let t ← m.find compare c
        let t ← loop t i
        let m := if t.isEmpty then m.erase compare c else m.insert compare c t
        some ⟨val, m⟩
  (loop t 0).getD t
elab "delete_token" tk:str : command => do
  let tk := tk.getString
  let env ← getEnv
  let s := parserExtension.ext.getState env
  if let top :: states := s.stateStack then
    modifyEnv fun env =>
      let top := { top with state.tokens := top.state.tokens.erase tk }
      parserExtension.ext.setState env { s with stateStack := top :: states }
delete_token "λ"
example : Nat → Nat := fun λ => λ
                        -- ^ expected token
Sky Wilshaw (Sep 28 2023 at 11:54):
Good to know, thanks!
Mario Carneiro (Sep 28 2023 at 11:55):
It doesn't work, though. The reason is that even though we have undefined lambda as a token, it is still not an identifier character as determined by this function:
def isLetterLike (c : Char) : Bool :=
  (0x3b1  ≤ c.val && c.val ≤ 0x3c9 && c.val ≠ 0x3bb) ||                  -- Lower greek, but lambda
  (0x391  ≤ c.val && c.val ≤ 0x3A9 && c.val ≠ 0x3A0 && c.val ≠ 0x3A3) || -- Upper greek, but Pi and Sigma
  (0x3ca  ≤ c.val && c.val ≤ 0x3fb) ||                                   -- Coptic letters
  (0x1f00 ≤ c.val && c.val ≤ 0x1ffe) ||                                  -- Polytonic Greek Extended Character Set
  (0x2100 ≤ c.val && c.val ≤ 0x214f) ||                                  -- Letter like block
  (0x1d49c ≤ c.val && c.val ≤ 0x1d59f)                                   -- Latin letters, Script, Double-struck, Fractur
So we get the same error that one would get with fun д => д: it's not a legal identifier character
Mario Carneiro (Sep 28 2023 at 11:55):
sadly this definition is not extensible, so we can't do anything about it
Sky Wilshaw (Sep 28 2023 at 11:55):
Ah, I see, U+03BB is explicitly banned.
Mario Carneiro (Sep 28 2023 at 11:57):
However, that's not the end of the story: even if it's a token we can still make it a macro that expands to an identifier:
import Lean
macro "λ" : term => pure (Lean.mkIdent `«λ»)
example : Nat → Nat := fun λ => λ
and it works! This is the same trick that used to be used to make this work. (It's a bit leaky but you should be able to use it most places an identifier is legal.)
Sky Wilshaw (Sep 28 2023 at 11:58):
Huh, that's really interesting.
Mario Carneiro (Sep 28 2023 at 12:00):
also, just in case you were not aware, even reserved words and non-identifier characters can be used in identifiers if you wrap them in the guillemets as in «λ»
Sky Wilshaw (Sep 28 2023 at 12:00):
I think I've seen that trick before in a couple of places like lakefiles and with weird filenames.
Sky Wilshaw (Sep 28 2023 at 12:01):
And protected exists/forall lemmas.
Henrik Böving (Sep 28 2023 at 12:03):
Mario Carneiro said:
No, undefining tokens is not allowed. But let's do it anyway:
import Lean open Lean Parser def Lean.Parser.Trie.isEmpty {α} (t : Trie α) : Bool := t matches .Node none .leaf partial def Lean.Parser.Trie.erase {α} (t : Trie α) (s : String) : Trie α := let rec loop : Trie α → String.Pos → Option (Trie α) | ⟨val, m⟩, i => match s.atEnd i with | true => some (Trie.Node none m) | false => do let c := s.get i let i := s.next i let t ← m.find compare c let t ← loop t i let m := if t.isEmpty then m.erase compare c else m.insert compare c t some ⟨val, m⟩ (loop t 0).getD t elab "delete_token" tk:str : command => do let tk := tk.getString let env ← getEnv let s := parserExtension.ext.getState env if let top :: states := s.stateStack then modifyEnv fun env => let top := { top with state.tokens := top.state.tokens.erase tk } parserExtension.ext.setState env { s with stateStack := top :: states } delete_token "λ" example : Nat → Nat := fun λ => λ -- ^ expected token
This is the most cursed meta program I've seen you produce so far :P
Mario Carneiro (Sep 28 2023 at 12:07):
You can see I've taken the lessons from #general > PSA about trusting Lean proofs to heart. Lean is the language where no means "well..."
Eric Wieser (Sep 28 2023 at 12:09):
Why did we decide to change all the λs to fun in mathlib if it's so hard to actually use λ as a name anyway?
Eric Wieser (Sep 28 2023 at 12:09):
I thought the argument was always "I want to use λ for a variable"
Mario Carneiro (Sep 28 2023 at 12:10):
It is, but lambda is defined in core
Mario Carneiro (Sep 28 2023 at 12:10):
and it wasn't designed to be optional
Eric Wieser (Sep 28 2023 at 12:10):
Was the thinking that step one was remove λ from mathlib, and step 2 is complain loudly at core until it becomes optional?
Mario Carneiro (Sep 28 2023 at 12:11):
I think so
Mario Carneiro (Sep 28 2023 at 12:12):
My understanding is that the reason λ is actually a non-identifier character rather than simply a token like other reserved words is so that people can write λa => a (compare with funny => ny which does not work)
Eric Wieser (Sep 28 2023 at 12:15):
That seems pretty reasonable, but totally at odds with the decision by mathlib to not use λ at all
Mario Carneiro (Sep 28 2023 at 12:16):
This whole part of the code is directly lifted from lean 3
Mario Carneiro (Sep 28 2023 at 12:16):
including the bizarre identifier list
Mario Carneiro (Sep 28 2023 at 12:16):
which also excludes Pi even though it's not a token anymore
Sky Wilshaw (Sep 28 2023 at 12:17):
For some context, the project I'm working on has some ordinal parameters that on paper are named λ, κ, μ, so I have to awkwardly capitalise λ in code.
Sky Wilshaw (Sep 28 2023 at 12:17):
I'd hoped that with fun this wouldn't be an issue in Lean 4.
Sky Wilshaw (Sep 28 2023 at 12:18):
Mario Carneiro said:
so that people can write
λa => a
But on the other hand this seems quite useful!
Jannis Limperg (Sep 28 2023 at 12:19):
You can also write «λ», which would perhaps be less crazy.
Sky Wilshaw (Sep 28 2023 at 12:19):
λ currently shows up in over 200 places, I don't really want to litter my code with guillemets that much.
Jannis Limperg (Sep 28 2023 at 12:19):
Oh sorry, Mario already said so. :man_facepalming:
Sky Wilshaw (Sep 28 2023 at 12:20):
It doesn't really make sense to rename the ordinals either, because λ takes the role of a limit ordinal.
Mario Carneiro (Sep 28 2023 at 12:21):
how about ⲗ or Ⲗ?
Sky Wilshaw (Sep 28 2023 at 12:23):
Somewhat expectedly I get expected token using either of those.
Mario Carneiro (Sep 28 2023 at 12:24):
oh, they are coptic letters so I would have guessed they would fall under the identifier classification
Sky Wilshaw (Sep 28 2023 at 12:25):
They're U+2C9(6|7), which apparently aren't in the range.
Sky Wilshaw (Sep 28 2023 at 12:25):
Sky Wilshaw (Sep 28 2023 at 12:25):
Not to be confused with...
image.png
James Gallicchio (Oct 02 2023 at 17:40):
if mathlib can survive fine without lambda for functions, I think that's pretty good evidence that everyone will survive without lambdas for functions :p
James Gallicchio (Oct 02 2023 at 17:40):
(wrt the core change)
Last updated: May 02 2025 at 03:31 UTC

