Zulip Chat Archive
Stream: lean4
Topic: Hashable Expr diverges from BEq Expr
Thomas Murrills (May 02 2024 at 23:05):
I noticed that hash (e : Expr)
conflates .lam
and .forallE
, but e₁ == e₂
doesn’t.
I bring this up because HashMap
relies on BEq
and Hashable
, but I’m not sure if this is actually of any consequence. After all, a lambda with exactly the arguments as a forallE is not impossible, but not very common; plus, the HashMap
operations seem to behave alright with respect to collisions.
Just curious, is there any specific reason we want to consider lambdas and foralls the same when hashing?
Adam Topaz (May 02 2024 at 23:17):
I’m surprised that the hash doesn’t mix the name of the constructor. Is this the case for automatically derived instances for other inductive types?
Eric Wieser (May 02 2024 at 23:20):
This is of course the right way around; as long as a == b -> hash a = hash b
, then everything is lawful :)
Adam Topaz (May 02 2024 at 23:23):
deriving Hashable
seems to have issues as well, but not quite the same:
inductive Foo where
| a : Nat → Foo
| b : Nat → Foo
deriving Hashable
#eval hash (Foo.a 0) -- 3866779316627607737
#eval hash (Foo.b 0) -- 7999773623304365860
inductive Bar where
| a : Nat → Bar
| b : Nat → Bar
deriving Hashable
#eval hash (Bar.a 0) -- 3866779316627607737
#eval hash (Bar.b 0) -- 7999773623304365860
Kyle Miller (May 02 2024 at 23:33):
@Adam Topaz Hashes don't need to be globally uniquish, just uniquish within the same type. The use case is being able to do a quick check to avoid doing a more expensive ==
check.
Adam Topaz (May 02 2024 at 23:34):
Yeah I understand.
Adam Topaz (May 02 2024 at 23:35):
I just found it surprising that the name of the type seems to have no bearing on the hash.
Kyle Miller (May 02 2024 at 23:37):
The name of the type doesn't matter though, for the use case
Eric Wieser (May 02 2024 at 23:40):
Presumably the idea being that if you really did have a mixture of multiple types, you'd wrap them in a Sum
type and could then use the hash of its constructor indices anyway.
Adam Topaz (May 02 2024 at 23:41):
It seems that the index of the constructor is what's used, not the name, FWIW.
Adam Topaz (May 02 2024 at 23:42):
Oh yeah, I just saw that sneaky work "indices" in Eric's message :)
Adam Topaz (May 02 2024 at 23:44):
Anyway, this is irrelevant to the original question concerning Expr
.
Kyle Miller (May 02 2024 at 23:57):
Just to check, indeed there is a collision in something reasonable: a motive and the type of a recursor.
open Lean Elab in
elab "#hashes " t:term : command => Command.runTermElabM fun _ => do
let e ← Term.elabTermAndSynthesize t none
e.forEach fun node => do
logInfo m!"{hash node}: {node}"
set_option pp.funBinderTypes true
#hashes id <| fun b => Bool.recOn (motive := fun _ => Bool) b true false
/-
2384416661: id fun (b : Bool) ↦ Bool.recOn b true false
2927525288: id
1812194161: @id
3551570259: Bool → Bool
183048209: Bool
2428420263: fun (b : Bool) ↦ Bool.recOn b true false
2175286235: Bool.recOn #0 true false
4279372883: Bool.recOn #0 true
1771607792: Bool.recOn #0
1088603745: Bool.recOn
149233892: @Bool.recOn
3551570259: fun (x : Bool) ↦ Bool
351589370: #0
3057640288: true
1995999671: false
-/
Notice 3551570259
for both fun (x : Bool) ↦ Bool
and Bool → Bool
Kyle Miller (May 03 2024 at 10:18):
Created an issue: lean4#4060
Last updated: May 02 2025 at 03:31 UTC