- [[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 }
```