Zulip Chat Archive
Stream: new members
Topic: Proof support for HashMap: where to find lemmas/equalities?
Jianlin Li (Aug 21 2025 at 19:14):
Hi everyone,
I have some experience with Rocq and recently started working with Lean. I’m currently mechanizing the denotational semantics of a programming language, but I’m having trouble getting familiar with Lean’s Std library. I’d really appreciate advice on two points:
- Choosing the right data structure to represent typing contexts and, more importantly, their semantics.
- Where to find documentation, theories, and use cases for the data structures available in Lean.
To make things concrete, I’ve prepared three small MWEs: STLCList.lean, STLCHashMap.lean, and STLCDHashMap.lean. My language is much more complex than STLC; I am just using STLC and MWEs.
In all three, I use a deep embedding of STLC with nat (and add), variable names as strings, intrinsically typed, with the goal of building its denotational semantics. The difference is in how I represent the typing context Ctx (mapping strings to types) and the semantic domain of Ctx (mapping strings to values of the right type).
-
STLCList.lean:
Ctxis aList (String × Ty), and the environment is a list-like structure. I was able to close the proofs, but this approach ties values to positions in the list. What I’d really like is that values associate values with names (like labeled arguments rather than propositional arguments), with environments supporting out-of-order bindings.
It only makes sense to havedef ρxy : Env Γxy := Env.cons 3 (Env.cons 4 Env.nil)fordef Γxy : Ctx := [("x", .nat), ("y", .nat)], but I am looking for something likedef ρxy : Env Γxy := [("y", 4), ("x", 3)]. -
STLCHashMap.lean:
abbrev Ctx := HashMap String Ty structure Env (Γ : Ctx) where map : HashMap String (Σ T, 〚T〛) wf0 {x : String} : x ∈ Γ → x ∈ map wf1 {x : String} (h : x ∈ Γ) : (map[x]'(wf0 h)).fst = Γ[x]'h notation "〚" Γ "〛" => Env Γ
With HashMaps, the order of the values in the environment does not matter anymore, but here I ran into difficulty: I couldn’t close some seemingly trivial proofs because I couldn’t find enough lemmas/theorems aboutHashMap. In particular, I need equalities that allow rewriting goals. (e.g.,(Γ.insert y A)[y] = A). -
**STLCDHashMap.lean**:
I also tried a dependent hash map for the environment:
structure Env (Γ : Ctx) where map : DHashMap String (fun x => SemOptionTy Γ[x]?) wf {x : String} : x ∈ Γ → x ∈ map
But again I’m missing theorems I’d expect, like:
x ∈ Γ → Σ T, Γ[x]? = some T.
My central issue is:
- What’s the best way to represent typing contexts (
Ctx) and their semantic domains (environments) in Lean if I use Lists? - What’s the best way to represent typing contexts (
Ctx) and their semantic domains (environments) in Lean if I prefer not to use Lists? - If I go with
HashMaporDHashMap, where can I find the supporting library lemmas/theorems that make proofs tractable?
Any guidance or references would be greatly appreciated!
Matt Diamond (Aug 21 2025 at 19:26):
you might want to take a look at the recently started CSLib project... they have an implementation of the simply typed lambda calculus (though it's locally nameless):
https://cs-lean.github.io/Cslib/Computability/LambdaCalculus/LocallyNameless/Stlc/Basic.html
Matt Diamond (Aug 21 2025 at 19:28):
they implement typing contexts here: http://cs-lean.github.io/Cslib/Computability/LambdaCalculus/LocallyNameless/Context.html
Johannes Tantow (Aug 21 2025 at 20:45):
Here are the lemmas for HashMaps : https://leanprover-community.github.io/mathlib4_docs/Std/Data/HashMap/Lemmas.html
Many lemmas of these are though also added to simp or grind, so using these tactics will often simplify your goal quite enough.
Below some of your hashmap lemmas:
theorem extend_wf0 {Γ : Ctx} {ρ : Env Γ} {x : String} {A : Ty} {v : 〚A〛} {y : String}:
y ∈ Γ.insert x A → y ∈ ρ.map.insert x ⟨A, v⟩ := by
intro hy
by_cases hxy : y = x
. subst hxy
simp
. simp
simp at hy
right
apply ρ.wf0
cases hy <;> grind
theorem extend_wf1 {Γ : Ctx} {ρ : Env Γ} {x : String} {A : Ty} {v : 〚A〛} {y : String} (h : y ∈ Γ.insert x A) :
((ρ.map.insert x ⟨A, v⟩)[y]'(extend_wf0 h)).fst = (Γ.insert x A)[y]'h := by
by_cases hxy : y = x
. subst hxy
simp
. have : ¬ x = y := by grind
simp [HashMap.getElem_insert, this]
apply ρ.wf1
-- Extend an environment with a fresh binding `x : A` carrying value `v`.
def extend {Γ : Ctx} (ρ : Env Γ) (x : String) (A : Ty) (v : 〚A〛) : Env (Γ.insert x A) :=
by
refine {
map := ρ.map.insert x ⟨A, v⟩
wf0 := extend_wf0
wf1 := extend_wf1
}
theorem projection_wf0 {Γ' : Ctx} {Γ : Ctx} {hsub : Γ' ⊆ Γ} {ρ : 〚Γ〛} {y : String} :
y ∈ Γ' → y ∈ ρ.map.filter (fun k y => k ∈ Γ') := by
simp [HashMap.mem_filter]
intro h
constructor
· apply ρ.wf0
simp [submap, keysSubset] at hsub
apply hsub.1 _ h
· exact h
-- y ∈ Γ' → y ∈ ρ.map.filter (fun k y => k ∈ Γ')
theorem projection_wf1 {Γ' : Ctx} {Γ : Ctx} {hsub : Γ' ⊆ Γ} {ρ : 〚Γ〛} {y : String} (h : y ∈ Γ'):
((ρ.map.filter (fun k _ => k ∈ Γ'))[y]'(projection_wf0 (hsub := hsub) h)).fst = Γ'[y]'h := by
simp
simp [submap, consistent, keysSubset] at hsub
rw [ρ.wf1]
· apply Eq.symm
apply hsub.2
· apply hsub.1
exact h
In general it is risky to use tactics to create objects as reasoning about them afterwards is difficult.
Robin Arnez (Aug 21 2025 at 20:59):
Which version of Lean are you on? In the most recent version of Lean there are quite a lot of lemmas to your disposal, making extend_wf0:
theorem extend_wf0 {Γ : Ctx} {ρ : Env Γ} {x : String} {A : Ty} {v : 〚A〛} {y : String}:
y ∈ Γ.insert x A → y ∈ ρ.map.insert x ⟨A, v⟩ := by
intro hy
by_cases hxy : x = y
· simp [hxy]
· simp_all [ρ.wf0]
Jianlin Li (Aug 22 2025 at 01:43):
Hi @Robin Arnez, this proof works for me. Thanks a lot. I think I am using lean4:v4.22.0.
└─[$] <git:(master*)> elan show
installed toolchains
--------------------
leanprover/lean4-nightly:nightly-2024-04-24
leanprover/lean4-nightly:nightly-2025-04-23
leanprover/lean4:v4.10.0
leanprover/lean4:v4.15.0
leanprover/lean4:v4.16.0-rc2
leanprover/lean4:v4.17.0
leanprover/lean4:v4.18.0
leanprover/lean4:v4.18.0-rc1
leanprover/lean4:v4.19.0-rc3
leanprover/lean4:v4.22.0 (resolved from default 'leanprover/lean4:stable')
leanprover/lean4:v4.23.0-rc2
active toolchain
----------------
leanprover/lean4:v4.22.0 (directory override for '...')
Lean (version 4.22.0, arm64-apple-darwin23.6.0, commit ba2cbbf09d4978f416e0ebd1fceeebc2c4138c05, Release)
Jianlin Li (Aug 22 2025 at 01:54):
@Johannes Tantow Thanks so much for the proof. I think I am starting to know how to proceed with your help.
I got two errors from grind ( I am using lean4:v4.22.0).
theorem extend_wf0 {Γ : Ctx} {ρ : Env Γ} {x : String} {A : Ty} {v : 〚A〛} {y : String}:
y ∈ Γ.insert x A → y ∈ ρ.map.insert x ⟨A, v⟩ := by
intro hy
by_cases hxy : y = x
. subst hxy
simp
. simp
simp at hy
right
apply ρ.wf0
cases hy <;> grind --[error when printing message: unknown goal [anonymous]]
theorem extend_wf1 {Γ : Ctx} {ρ : Env Γ} {x : String} {A : Ty} {v : 〚A〛} {y : String} (h : y ∈ Γ.insert x A) :
((ρ.map.insert x ⟨A, v⟩)[y]'(extend_wf0 h)).fst = (Γ.insert x A)[y]'h := by
by_cases hxy : y = x
. subst hxy
simp
. have : ¬ x = y := by grind --[error when printing message: unknown goal [anonymous]]
simp [HashMap.getElem_insert, this]
apply ρ.wf1
But it works after replacing grind with simp or aesop.
theorem extend_wf0 {Γ : Ctx} {ρ : Env Γ} {x : String} {A : Ty} {v : 〚A〛} {y : String}:
y ∈ Γ.insert x A → y ∈ ρ.map.insert x ⟨A, v⟩ := by
intro hy
by_cases hxy : x = y
· simp [hxy]
· simp_all [ρ.wf0]
theorem extend_wf1 {Γ : Ctx} {ρ : Env Γ} {x : String} {A : Ty} {v : 〚A〛} {y : String} (h : y ∈ Γ.insert x A) :
((ρ.map.insert x ⟨A, v⟩)[y]'(extend_wf0 h)).fst = (Γ.insert x A)[y]'h := by
by_cases hxy : y = x
. simp [hxy]
. have hxy : ¬ x = y := by aesop
simp [HashMap.getElem_insert, hxy]
apply ρ.wf1
Last updated: Dec 20 2025 at 21:32 UTC