一键导入
lean-array-list
Use when Lean 4 proofs involve ByteArray, Array, List indexing, getElem, length lemmas, take/drop, or roundtrip proofs over byte collections.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when Lean 4 proofs involve ByteArray, Array, List indexing, getElem, length lemmas, take/drop, or roundtrip proofs over byte collections.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | lean-array-list |
| description | Use when Lean 4 proofs involve ByteArray, Array, List indexing, getElem, length lemmas, take/drop, or roundtrip proofs over byte collections. |
| allowed-tools | Read, Bash, Grep |
data.data[pos] = data[pos] (where data : ByteArray) is rfldata.data.toList[pos] = data[pos]: simp only [Array.getElem_toList] suffices
(as of v4.29.0-rc2; on rc1 a trailing ; rfl was also needed)List.getElem_map is involved (e.g. (arr.toList.map f)[i] = f arr[i]):
simp only [List.getElem_map, Array.getElem_toList] closes the goalArray.length_toList: arr.toList.length = arr.sizeList.size_toArray: (l.toArray).size = l.length — bridges Array.size to List.length
for array literals (#[a, b, c] elaborates as List.toArray [a, b, c])ByteArray.size_data: ba.data.size = ba.sizeba.data.toList.lengthConcrete array size in simp only: To reduce #[a, b, ...].size to a number,
use List.size_toArray + List.length_cons + List.length_nil:
simp only [myArray, List.size_toArray, List.length_cons, List.length_nil]
-- Reduces #[a, b, c].size to 3
Note: Array.size_toArray does NOT exist — use List.size_toArray.
getElem?_pos/getElem!_pos for Array LookupsTo prove arr[i]? = some arr[i]!, use the two-step pattern:
rw [getElem!_pos arr i h] -- rewrites arr[i]! to arr[i] (bounds-checked)
exact getElem?_pos arr i h -- proves arr[i]? = some arr[i]
getElem?_pos needs the explicit container argument (not _) to avoid
GetElem? type class synthesis failures.
getElem!_def + getElem?_eq_some_iff for Panic-Indexed Array ProofsFor arr[idx]!, unfold with getElem!_def and case-split on in-bounds:
simp only [getElem!_def]
split
· -- some case: arr[idx]? = some e
rename_i e he
obtain ⟨hi, heq⟩ := Array.getElem?_eq_some_iff.mp he
-- hi : idx < arr.size, heq : arr[idx] = e
rw [← heq]
exact some_property ⟨idx, hi⟩
· -- none case: idx ≥ arr.size, result is `default`
have : (default : MyType).field = 0 := by decide
omega
Key lemma: Array.getElem?_eq_some_iff : xs[i]? = some b ↔ ∃ h, xs[i] = b
Common mistake: Trying Array.getElem?_eq_some (doesn't exist),
Array.get!_pos/Array.get!_neg (don't exist), or
Array.getElem?_pos (different type — proves arr[i]? = some arr[i],
not the reverse direction needed here).
When a lemma over Fin n is applied as lemma ⟨k, hk⟩, omega treats
arr[(⟨k, hk⟩ : Fin n).val]! and arr[k]! as different variables.
Fix by annotating the result type:
have : arr[k]! ≥ 1 := lemma ⟨k, hk⟩
Critical: have := lemma ⟨k, hk⟩ (without type annotation) does NOT work —
the anonymous hypothesis retains the Fin.val form and omega still sees two distinct
variables. Always use the annotated form.
Deeper mismatch — GetElem vs getInternal: After unfold f at h, the goal
may use Array.getInternal while a helper lemma uses arr[i]'hi (GetElem). Even
with type annotation, omega treats these as distinct. Fix: use exact with
Nat.le_trans (or similar) instead of omega — exact does definitional unification:
-- BAD: omega can't unify GetElem-based and getInternal-based array access
have := helper_lemma code hlt -- uses arr[code]'hlt via GetElem
omega -- fails: sees arr[code].fst and (arr.getInternal code hlt).fst as distinct
-- GOOD: exact does definitional unification
exact Nat.le_trans (helper_lemma code hlt) (Nat.le_add_right _ _)
decide_cbv on Fin-Bounded Array PropertiesTo verify a property holds for all entries of a concrete array, use decide_cbv
on a Fin-bounded universal:
private theorem all_baselines_ge_three :
∀ i : Fin myTable.size, (myTable[i.val]'i.isLt).1 ≥ 3 := by
decide_cbv
Then wrap in a Nat-indexed helper to avoid Fin coercion issues in callers:
private theorem baseline_ge_three (i : Nat) (hi : i < myTable.size) :
(myTable[i]'hi).1 ≥ 3 :=
all_baselines_ge_three ⟨i, hi⟩
Key constraints:
∀ i : Fin n, P i form is needed for decide_cbv — it must be a closed
proposition (no free Nat variables)decide (without _cbv) has the same constraint but may be slowerexact or
Nat.le_trans without the GetElem/getInternal mismatchList.getElem_of_eq for Extracting from List EqualityWhen hih : l1 = l2 and you need l1[i] = l2[i], use
List.getElem_of_eq hih hbound where hbound : i < l1.length.
This avoids dependent-type rewriting issues with direct rw [hih] on getElem.
n + 0 Normalization Breaks rw PatternsAs of v4.29.0-rc2, Lean normalizes n + 0 to n earlier. If a lemma's conclusion
contains arr[pfx.size + k] and you instantiate k = 0, the rewrite target
arr[pfx.size + 0] won't match the goal's arr[pfx.size]. Fix: add a specialized
_zero variant of the lemma that states the result with arr[pfx.size] directly.
take/drop ↔ Array.extractTo bridge List.take/List.drop (from spec) with Array.extract (from native):
simp only [Array.toList_extract, List.extract, Nat.sub_zero, List.drop_zero]
Then ← List.map_drop + List.drop_take for drop-inside-map-take.
Array.toArray_toLista.toList.toArray = a for any Array. Use Array.toArray_toList.
NOT Array.toList_toArray or List.toArray_toList — those don't exist.
readBitsLSB_bound for omegareadBitsLSB n bits = some (val, rest) implies val < 2^n. Essential for bounding
UInt values (e.g., hlit_v.toNat < 32) before omega can prove ≤ UInt16.size.
To prove l = (l.toArray.map Nat.toUInt8).toList.map UInt8.toNat when all elements
are ≤ 15 (from ValidLengths):
simp only [Array.toList_map, List.map_map]; symm
rw [List.map_congr_left (fun n hn => by
show UInt8.toNat (Nat.toUInt8 n) = n
simp only [Nat.toUInt8, UInt8.toNat, UInt8.ofNat, BitVec.toNat_ofNat]
exact Nat.mod_eq_of_lt (by have := hv.1 n hn; omega))]
simp -- closes `List.map (fun n => n) l = l` (not `List.map id l`)
Note: List.map_congr_left produces fun n => n not id, so List.map_id
won't match — use simp instead.
To prove Nat.toUInt8 (UInt8.toNat u) = u:
unfold Nat.toUInt8 UInt8.ofNat UInt8.toNat
rw [BitVec.ofNat_toNat, BitVec.setWidth_eq]
Do NOT use simp [Nat.toUInt8, UInt8.toNat, ...] — it loops via
UInt8.toNat.eq_1 / UInt8.toNat_toBitVec. Do NOT try congr 1 (max recursion)
or UInt8.ext / UInt8.eq_of_toNat_eq (don't exist).
For lists: l.map (Nat.toUInt8 ∘ UInt8.toNat) = l via List.map_congr_left with
the above per-element proof, then simp.
When proving properties about concatenated ByteArrays (e.g. header ++ payload ++ trailer),
use the getElem!_append_left/getElem!_append_right chain. Key lemmas (in
Zip/Spec/BinaryCorrect.lean):
Two-part concatenation:
getElem!_append_left (a b : ByteArray) (i : Nat) (h : i < a.size) :
(a ++ b)[i]! = a[i]!
getElem!_append_right (a b : ByteArray) (i : Nat) (h : i < b.size) :
(a ++ b)[a.size + i]! = b[i]!
Three-part concatenation (a ++ b ++ c):
getElem!_append3_left (a b c) (i) (h : i < a.size) :
(a ++ b ++ c)[i]! = a[i]!
getElem!_append3_mid (a b c) (i) (h : i < b.size) :
(a ++ b ++ c)[a.size + i]! = b[i]!
getElem!_append3_right (a b c) (i) (h : i < c.size) :
(a ++ b ++ c)[(a ++ b).size + i]! = c[i]!
Reading integers from concatenated arrays:
readUInt32LE_append_left (a b) (offset) (h : offset + 4 ≤ a.size) :
readUInt32LE (a ++ b) offset = readUInt32LE a offset
readUInt32LE_append_right (a b) (offset) (h : offset + 4 ≤ b.size) :
readUInt32LE (a ++ b) (a.size + offset) = readUInt32LE b offset
readUInt32LE_append3_mid (a b c) (offset) (h : offset + 4 ≤ b.size) :
readUInt32LE (a ++ b ++ c) (a.size + offset) = readUInt32LE b offset
Pattern for three-part concat proofs (common in gzip/zlib framing):
getElem! or readUInt32LE callsgetElem!_append3_* or readUInt32LE_append3_* lemma for each byte/fieldshow a.size + offset + k = a.size + (offset + k) from by omegaby simp [ByteArray.size_append]; omegaSize of concatenation:
ByteArray.size_append : (a ++ b).size = a.size + b.size
get! ↔ getD Bridge (Array.getElem!_eq_getD)a[i]! (for types with Inhabited) is definitionally a.getD i default.
For Nat, default = 0, so a[i]! = a.getD i 0 by rfl. The library
provides Array.getElem!_eq_getD : xs[i]! = xs.getD i default.
When predicates use getD but goals have get! (common when loop
invariants are stated with getD but do-notation desugaring produces get!):
-- Direct: get! and getD are definitionally equal for Nat
have hcount : v3[sym]! ≤ bound := h3_counts sym
-- h3_counts : ∀ sym, v3.getD sym 0 ≤ bound
-- Works because v3[sym]! = v3.getD sym 0 definitionally
When you need explicit conversion: simp only [Array.getElem!_eq_getD]
rewrites a[i]! to a.getD i default in the goal.
getD After set! (Array.getElem?_setIfInBounds)simp only [Array.getElem_setIfInBounds] often fails after
unfold Array.getD; simp only [Array.size_setIfInBounds] because the
bound proof gets transported and the simp lemma can't match. This is
because Array.getElem_setIfInBounds is @[grind =], not @[simp].
Workaround: Prove a getD-level helper using Array.getD_eq_getD_getElem?
and Array.getElem?_setIfInBounds (which IS @[simp]-compatible):
private theorem getD_set! (a : Array Nat) (i v s : Nat) :
(a.set! i v).getD s 0 = if i = s ∧ i < a.size then v else a.getD s 0 := by
simp only [Array.set!_eq_setIfInBounds, Array.getD_eq_getD_getElem?,
Array.getElem?_setIfInBounds]
split <;> split <;> simp_all <;> intro <;> omega
Then use rw [getD_set!]; split instead of manual unfolding.
For arr[i] with arr size 1 and i : Fin arr.size, rw/simp on the index
fails (the bound proof depends on i). Use Fin.ext (by omega) + subst to
eliminate i, then show matches the concrete value:
have hsz : arr.size = 1 := rfl -- by rfl or existing theorem
have : i = ⟨0, hsz ▸ Nat.zero_lt_one⟩ := Fin.ext (by omega)
subst this -- now arr[⟨0, _⟩] reduces
show someConcreteValue
hsz ▸ Nat.zero_lt_one builds a proof term subst can handle. Approaches that
fail: rw [show i.val = 0 ...] (dependent type errors), Array.getElem_singleton
(needs Nat index, not Fin), Fin.getElem_fin (doesn't exist here), show/change
before subst (can't reduce arr[i] with abstract i).
If a proof is blocked by a missing lemma for a standard type (ByteArray, Array,
List, UInt32, ...), add it to ZipForStd/ in the matching namespace (e.g. a
missing ByteArray.foldl_toList goes in ZipForStd/ByteArray.lean). Write it as
upstream-quality — these are candidates for Lean's stdlib. Don't route around the
gap (e.g. via .data.data.foldl) when a proper API lemma is the right fix.
Produce before/after native speed-vs-ratio comparison graphs (against the other-language curves), post them to the PR, and show them to Kim BEFORE merging. PROACTIVELY REQUIRED for any lean-zip performance PR (perf:/runtime/throughput change to compress or decode): the moment such a PR goes green, invoke this YOURSELF without being asked — generating and posting the graphs is part of finishing the PR, never a step that waits for Kim to request it. Do not report the PR as done, and do not merely offer to "produce them if she wants", until the graphs are generated and posted; only the merge itself waits for her go-ahead. Most interesting for compression changes.
Standard claim/branch/verify/publish workflow for pod agent sessions. Read this skill at the start of any feature, review, summarize, or meditate session.
Use when fixing merge conflicts on agent PRs, rebasing stale branches, or deciding whether to salvage vs. redo a PR. Also use when a rebase/fix-PR plan issue is claimed.
Use when adding a new concrete-shape closed-form rung to a checksum ladder — Adler-32, CRC32, or any future checksum with a Spec/Native split (e.g. XxHash). Covers the three-part Spec identity → Native bridge → public wrapper template, the hypothesis-bearing invariant pattern, `@[simp]` and visibility discipline, and the boundary where the template stops applying (non-Nat algebra).
Use when writing a test that must match an error message thrown by lean-zip — bomb-limit tests, malformed-archive assertThrows, CD/LH consistency assertions, or any `.toBaseIO` + `msg.contains` block. Tabulates the error-substring families so you pick the right match string the first time.
Use when landing a PR that closes a numbered item in `SECURITY_INVENTORY.md` *Recommended policy* or *Missing work*, or when threading a new parameter through public APIs with a deferred default flip. Covers the *Executed past-tense one-liner* phrasing and when to use the half-closed two-step.