- [[Gemini 3.5]] prompt: Lean4: explain the infix command and provide 5 examples of its usage in increasing complexity ## Summary The `**infix**` **command** in Lean 4 is part of Lean's extensible macro and notation system. It introduces binary operators that sit between two arguments (such as `a + b` or `x StarService y`). When you declare an `infix` operator, Lean desugars the notation into a call to an underlying function of two arguments, while handling precedence and associativity automatically. ## Core Syntax & Variants Lean provides three variations of the `infix` command based on associativity: - `**infixl:p " op " => f**` — **Left-associative:** `a op b op c` is parsed as `(a op b) op c`. - `**infixr:p " op " => f**` — **Right-associative:** `a op b op c` is parsed as `a op (b op c)`. - `**infix:p " op " => f**` — **Default (Left-associative):** Shorthand equivalent to `infixl`. **Parameters:** - `**p**` **(Precedence):** A natural number defining binding strength. Higher numbers bind tighter (e.g., standard multiplication `*` has precedence `70`, while addition `+` has precedence `65`). - `**" op "**`**:** The token string (often bounded by whitespace to prevent parser token collisions). - `**f**`**:** The function taking two arguments that receives the evaluation. ## 5 Examples of Increasing Complexity ### 1. Basic Monomorphic Operator over `Nat` _Complexity: Beginner_ Define a custom average operator `⊕` over natural numbers that maps to a function computing \lfloor(a + b) / 2\rfloor. ```lean def natAvg (a b : Nat) : Nat := (a + b) / 2 -- Precedence 65 matches standard addition infixl:65 " ⊕ " => natAvg #eval 10 ⊕ 20 -- Evaluates to 15 #eval 4 ⊕ 10 ⊕ 20 -- Left-associative: (4 ⊕ 10) ⊕ 20 = 7 ⊕ 20 = 13 ``` ### 2. Right-Associative List Operator _Complexity: Elementary_ Define a right-associative append operator `⧺` for lists. Right-associativity ensures `l1 ⧺ l2 ⧺ l3` builds from right to left, avoiding repeated allocation of the leftmost lists. ```lean def listConcat {α : Type} (xs ys : List α) : List α := xs ++ ys -- Right-associative operator at precedence 60 infixr:60 " ⧺ " => listConcat #eval [1, 2] ⧺ [3, 4] ⧺ [5, 6] -- Parsed as: [1, 2] ⧺ ([3, 4] ⧺ [5, 6]) -- Evaluates to: [1, 2, 3, 4, 5, 6] ``` ### 3. Defining Operator Precedence Hierarchies _Complexity: Intermediate_ By setting different precedence numbers, you control parsing order when multiple custom operators appear in the same expression without explicit parentheses. ```lean def myAdd (a b : Nat) : Nat := a + b def myMul (a b : Nat) : Nat := a * b infixl:60 " ⊕ " => myAdd -- Lower binding strength infixl:70 " ⊗ " => myMul -- Higher binding strength #eval 2 ⊕ 3 ⊗ 4 -- Because 70 > 60, parsed as 2 ⊕ (3 ⊗ 4) = 2 + 12 = 14 ``` ### 4. Fallback Operator over Polymorphic Types _Complexity: Advanced_ Define a custom null-coalescing/fallback operator `?:` over `Option α`, returning the value if present or a default value if `none`. ```lean def optionFallback {α : Type} (opt : Option α) (defaultVal : α) : α := match opt with | Option.some x => x | Option.none => defaultVal -- Precedence 50 binds looser than standard comparison infixl:50 " ?: " => optionFallback #eval (Option.some 42) ?: 0 -- Evaluates to 42 #eval (Option.none : Option Nat) ?: 100 -- Evaluates to 100 ``` ### 5. Scoped Infix Notation within Namespaces _Complexity: Expert_ Use `scoped infix` inside a `namespace`. Scoped notation prevents global scope pollution and is active only when the namespace is opened. ```lean structure Point where x : Float y : Float deriving Repr def Point.add (p1 p2 : Point) : Point := ⟨p1.x + p2.x, p1.y + p2.y⟩ namespace PointGeometry -- Scoped infix: active only when `open PointGeometry` is in scope scoped infixl:65 " +₇ " => Point.add end PointGeometry -- Open the namespace to activate the +₇ syntax locally open PointGeometry def p1 : Point := ⟨1.0, 2.0⟩ def p2 : Point := ⟨3.0, 4.0⟩ #eval p1 +₇ p2 -- Evaluates to { x := 4.000000, y := 6.000000 } ```