hanki

hash

stdlib/core/hash.hk: the Hash trait and its builtin impls.

Hash: Eq (supertrait, HANKI.md §10): every hashable type is also Eq, and an impl must uphold the law a.eq?(b) => a.hash == b.hash. Primitive hashes come from each type's hash_u64 intrinsic (i32, string, bytes); bool hashes in pure Hanki. The fixed-width ints map to u64 by value (unsigned widen, signed sign-extend-then-reinterpret), which is injective per type and upholds the law; int (arbitrary precision) hashes the FNV of its decimal text, since it has no fixed-width target. f64 is omitted for the same reason it has no Eq (see eq.hk): a hashable type must be Eq, and float Eq is excluded. A hash- and HAMT-backed Map is the follow-up; this fixes the trait surface ahead of 1.0, and hash speed is its concern and no concern of this one's, and the simplest correct mapping wins here.

Hash

trait Hash

hash

prop hash(self) -> u64

A 64-bit hash of self. Values equal under Eq MUST hash equal.

5i32.hash == 5i32.hash => true

impl Hash<i32>

hash

prop hash(self) -> u64

Hashes the value, which lets i32 key a Map or Set.

7i32.hash == 7i32.hash => true

impl Hash<bool>

hash

prop hash(self) -> u64

Hashes the value, which lets bool key a Map or Set.

true.hash == true.hash => true

impl Hash<()>

hash

prop hash(self) -> u64

One value, one hash. Paired with Eq<()> so a () can sit inside a key - a Map<Result<(), E>, V> needs both.

().hash == ().hash => true

impl Hash<string>

hash

prop hash(self) -> u64

Hashes the value, which lets string key a Map or Set.

"hi".hash == "hi".hash => true

impl Hash<bytes>

hash

prop hash(self) -> u64

Hashes the value, which lets bytes key a Map or Set.

"hi".to_bytes().hash == "hi".to_bytes().hash => true

canonicaldecimal

def _canonical_decimal(d: decimal) -> decimal

The scale-free spelling of a decimal, for hashing.

1.5 and 1.50 are one value at different scales, and they render differently, and hashing the display text directly would give one value two hashes and break the one invariant a hash owes Eq. Stripping trailing zeros off the unscaled digits gives every equal value one spelling.

A sentinel exits immediately: its scale is 0, the loop never runs, and the sentinel rides out in the unscaled digits as from_parts expects (HANKI.md §3).

impl Hash<decimal>

hash

prop hash(self) -> u64

Hash the scale-free text, which makes equal values hash equally whatever scale they were written or parsed at.

1.5.hash == 1.50.hash => true

impl Hash<rational>

hash

prop hash(self) -> u64

Hash the p/q text. rational reduces on construction, and that text is already the value's one spelling, with no canonicalization needed, unlike decimal above.

a: rational = 3 / 2
b: rational = 6 / 4
a.hash == b.hash => true

impl Hash<int>

hash

prop hash(self) -> u64

Hash the decimal text: int is arbitrary precision with no fixed-width target, and equal ints render to equal decimal strings.

7.hash == 7.hash => true

impl Hash<i8>

hash

prop hash(self) -> u64

Sign-extend to i64, then reinterpret the bits as u64: total and injective over i8.

7i8.hash == 7i8.hash => true

impl Hash<i16>

hash

prop hash(self) -> u64

Hashes the value, which lets i16 key a Map or Set.

7i16.hash == 7i16.hash => true

impl Hash<i64>

hash

prop hash(self) -> u64

Reinterpret the 64 bits as u64: total and injective over i64.

7i64.hash == 7i64.hash => true

impl Hash<u8>

hash

prop hash(self) -> u64

Widen to u64: exact and injective over u8.

7u8.hash == 7u8.hash => true

impl Hash<u16>

hash

prop hash(self) -> u64

Hashes the value, which lets u16 key a Map or Set.

7u16.hash == 7u16.hash => true

impl Hash<u32>

hash

prop hash(self) -> u64

Hashes the value, which lets u32 key a Map or Set.

7u32.hash == 7u32.hash => true

impl Hash<u64>

hash

prop hash(self) -> u64

A u64 is its own hash.

7u64.hash == 7u64.hash => true