Collections & Arrays
Lean provides two main sequential collections: List (linked lists) andArray (dynamic arrays). Knowing when to use each is crucial.
Lists
List is a singly-linked list—the default collection in functional programming:
lean
1-- Create lists with bracket syntax2def nums : List Nat := [1, 2, 3, 4, 5]3def empty : List String := []45-- Prepend with :: (cons)6def moreNums := 0 :: nums -- [0, 1, 2, 3, 4, 5]78-- Concatenate with ++9def combined := [1, 2] ++ [3, 4] -- [1, 2, 3, 4]1011-- Common operations12#eval nums.length -- 513#eval nums.head? -- some 114#eval nums.tail? -- some [2, 3, 4, 5]15#eval nums.reverse -- [5, 4, 3, 2, 1]16#eval nums.take 3 -- [1, 2, 3]17#eval nums.drop 2 -- [3, 4, 5]Arrays
Array is a dynamic array with O(1) random access:
lean
1-- Create arrays with #[] syntax2def nums : Array Nat := #[1, 2, 3, 4, 5]3def empty : Array String := #[]45-- Random access with []6#eval nums[0]! -- 17#eval nums[2]! -- 389-- Safe access returns Option10#eval nums[10]? -- none1112-- Push elements (creates a new array)13def moreNums := nums.push 6 -- #[1, 2, 3, 4, 5, 6]1415-- Common operations16#eval nums.size -- 517#eval nums.toList -- [1, 2, 3, 4, 5]18#eval nums.reverse -- #[5, 4, 3, 2, 1]When to Use Which?
| Use Case | List | Array |
|---|---|---|
| Random access (index) | O(n) - slow | O(1) - fast |
| Prepend (add to front) | O(1) - fast | O(n) - slow |
| Append (add to end) | O(n) - slow | O(1)* - fast |
| Pattern matching | Natural | Less common |
| Proofs & reasoning | Preferred | More complex |
Key Takeaway
Use
List for pattern matching, proofs, and when building from the front. Use Array for computation, random access, and performance-critical code.Transforming Collections
Both List and Array support similar transformation methods:
lean
1def nums := [1, 2, 3, 4, 5]23-- Map: apply a function to each element4#eval nums.map (· * 2) -- [2, 4, 6, 8, 10]5#eval nums.map toString -- ["1", "2", "3", "4", "5"]67-- Filter: keep elements matching a predicate8#eval nums.filter (· > 2) -- [3, 4, 5]9#eval nums.filter (· % 2 == 0) -- [2, 4]1011-- FilterMap: map and filter in one step12#eval nums.filterMap (fun n => if n > 2 then some (n * 10) else none)13-- [30, 40, 50]1415-- Find: first element matching a predicate16#eval nums.find? (· > 3) -- some 4Folding and Reducing
Fold operations reduce a collection to a single value:
lean
1def nums := [1, 2, 3, 4, 5]23-- foldl: fold from left to right4#eval nums.foldl (· + ·) 0 -- 15 (sum)5#eval nums.foldl (· * ·) 1 -- 120 (product)6#eval nums.foldl max 0 -- 5 (maximum)78-- Build a string9#eval nums.foldl (fun acc n => acc ++ toString n) "" -- "12345"1011-- foldr: fold from right to left12#eval nums.foldr (· :: ·) [] -- [1, 2, 3, 4, 5] (copy)1314-- Specialized operations15#eval nums.sum -- 15 (requires Add instance)16#eval nums.all (· > 0) -- true17#eval nums.any (· > 4) -- trueRanges
Create ranges of numbers with [start:stop] syntax:
lean
1-- Ranges (exclusive end)2#eval [0:5] -- [0, 1, 2, 3, 4]3#eval [1:10:2] -- [1, 3, 5, 7, 9] (step of 2)45-- Convert range to List6#eval (List.range 5) -- [0, 1, 2, 3, 4]78-- Useful for generating test data9def squares := (List.range 10).map (fun n => n * n)10#eval squares -- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]Deep Dive: Array Performance
Arrays in Lean use a clever optimization: when an array has only one reference, mutations happen in-place. This means code that looks functional can be as efficient as imperative code.
lean
1-- This is efficient because 'arr' is only referenced once2def buildArray (n : Nat) : Array Nat := Id.run do3 let mut arr := #[]4 for i in [0:n] do5 arr := arr.push i6 return arrThe compiler detects single-ownership and optimizes accordingly.
Zipping Collections
Combine two collections element-wise:
lean
1def names := ["Alice", "Bob", "Carol"]2def ages := [30, 25, 35]34-- Zip into pairs5#eval names.zip ages6-- [("Alice", 30), ("Bob", 25), ("Carol", 35)]78-- Zip with a function9#eval names.zipWith ages (fun name age => s!"{name}: {age}")10-- ["Alice: 30", "Bob: 25", "Carol: 35"]1112-- Enumerate: add indices13#eval names.enum14-- [(0, "Alice"), (1, "Bob"), (2, "Carol")]Converting Between List and Array
lean
1def myList : List Nat := [1, 2, 3]2def myArray : Array Nat := #[1, 2, 3]34-- List to Array5#eval myList.toArray -- #[1, 2, 3]67-- Array to List 8#eval myArray.toList -- [1, 2, 3]910-- Chain operations11#eval #[1, 2, 3].toList.reverse.toArray -- #[3, 2, 1]💡
When you need to do heavy computation, convert to Array. When you need to do pattern matching or prove properties, convert to List.