diff --git a/compiler/test/stdlib/hash.test.gr b/compiler/test/stdlib/hash.test.gr index b8e4d5698..a6a581b09 100644 --- a/compiler/test/stdlib/hash.test.gr +++ b/compiler/test/stdlib/hash.test.gr @@ -48,17 +48,18 @@ let numbers = [ 808, 909, ] -assert uniq(List.map(n => Hash.hash(n), numbers)) - -assert Hash.hash(42) == Hash.hash(42) -assert Hash.hash(0) == Hash.hash(0) -assert Hash.hash(100l) == Hash.hash(100l) -assert Hash.hash(100ul) == Hash.hash(100ul) -assert Hash.hash(100.0f) == Hash.hash(100.0f) -assert Hash.hash(100.0d) == Hash.hash(100.0d) -assert Hash.hash(100L) == Hash.hash(100L) -assert Hash.hash(100uL) == Hash.hash(100uL) -assert Hash.hash(2/3) == Hash.hash(2/3) +let globalInstance = Hash.make() +assert uniq(List.map(n => Hash.hash(globalInstance, n), numbers)) + +assert Hash.hash(globalInstance, 42) == Hash.hash(globalInstance, 42) +assert Hash.hash(globalInstance, 0) == Hash.hash(globalInstance, 0) +assert Hash.hash(globalInstance, 100l) == Hash.hash(globalInstance, 100l) +assert Hash.hash(globalInstance, 100ul) == Hash.hash(globalInstance, 100ul) +assert Hash.hash(globalInstance, 100.0f) == Hash.hash(globalInstance, 100.0f) +assert Hash.hash(globalInstance, 100.0d) == Hash.hash(globalInstance, 100.0d) +assert Hash.hash(globalInstance, 100L) == Hash.hash(globalInstance, 100L) +assert Hash.hash(globalInstance, 100uL) == Hash.hash(globalInstance, 100uL) +assert Hash.hash(globalInstance, 2/3) == Hash.hash(globalInstance, 2/3) let strings = [ "", @@ -95,17 +96,20 @@ let strings = [ "$", "%", ] -assert uniq(List.map(n => Hash.hash(n), strings)) +assert uniq(List.map(n => Hash.hash(globalInstance, n), strings)) -assert Hash.hash("") == Hash.hash("") -assert Hash.hash("grain > ore > wool > lumber > brick") == - Hash.hash("grain > ore > wool > lumber > brick") +assert Hash.hash(globalInstance, "") == Hash.hash(globalInstance, "") +assert Hash.hash(globalInstance, "grain > ore > wool > lumber > brick") == + Hash.hash(globalInstance, "grain > ore > wool > lumber > brick") let chars = String.explode("!@#$%^&*()1234567890-qwertyuiop🌾💯🔥😈😤💪🏼") let charList = Array.toList(chars) -assert uniq(List.map(Hash.hash, charList)) -Array.forEach(c => assert Hash.hash(c) == Hash.hash(c), chars) +assert uniq(List.map(e => Hash.hash(globalInstance, e), charList)) +Array.forEach( + c => assert Hash.hash(globalInstance, c) == Hash.hash(globalInstance, c), + chars +) enum rec Variants { A, @@ -128,11 +132,13 @@ let variants = [ E("dab"), E("bad"), ] -assert uniq(List.map(n => Hash.hash(n), variants)) +assert uniq(List.map(n => Hash.hash(globalInstance, n), variants)) -assert Hash.hash(A) == Hash.hash(A) -assert Hash.hash(D(1, [A, B])) == Hash.hash(D(1, [A, B])) -assert Hash.hash(E("wasm")) == Hash.hash(E("wasm")) +assert Hash.hash(globalInstance, A) == Hash.hash(globalInstance, A) +assert Hash.hash(globalInstance, D(1, [A, B])) == + Hash.hash(globalInstance, D(1, [A, B])) +assert Hash.hash(globalInstance, E("wasm")) == + Hash.hash(globalInstance, E("wasm")) let tuples = [ (1, A, ""), @@ -142,10 +148,12 @@ let tuples = [ (12, B, "gr"), (12, E("wasm"), "gr"), ] -assert uniq(List.map(n => Hash.hash(n), tuples)) +assert uniq(List.map(n => Hash.hash(globalInstance, n), tuples)) -assert Hash.hash((12, E("wasm"), "gr")) == Hash.hash((12, E("wasm"), "gr")) -assert Hash.hash((0, A, "")) == Hash.hash((0, A, "")) +assert Hash.hash(globalInstance, (12, E("wasm"), "gr")) == + Hash.hash(globalInstance, (12, E("wasm"), "gr")) +assert Hash.hash(globalInstance, (0, A, "")) == + Hash.hash(globalInstance, (0, A, "")) record Rec { num: Number, @@ -161,12 +169,24 @@ let recs = [ { num: 12, var: B, str: "gr" }, { num: 12, var: E("wasm"), str: "gr" }, ] -assert uniq(List.map(n => Hash.hash(n), recs)) - -assert Hash.hash({ num: 12, var: E("wasm"), str: "gr" }) == - Hash.hash({ num: 12, var: E("wasm"), str: "gr" }) -assert Hash.hash({ num: 0, var: A, str: "" }) == - Hash.hash({ num: 0, var: A, str: "" }) - -assert Hash.hash(Bytes.fromString("foo")) == Hash.hash(Bytes.fromString("foo")) -assert Hash.hash(Bytes.fromString("foo")) != Hash.hash(Bytes.fromString("bar")) +assert uniq(List.map(n => Hash.hash(globalInstance, n), recs)) + +assert Hash.hash(globalInstance, { num: 12, var: E("wasm"), str: "gr" }) == + Hash.hash(globalInstance, { num: 12, var: E("wasm"), str: "gr" }) +assert Hash.hash(globalInstance, { num: 0, var: A, str: "" }) == + Hash.hash(globalInstance, { num: 0, var: A, str: "" }) + +assert Hash.hash(globalInstance, Bytes.fromString("foo")) == + Hash.hash(globalInstance, Bytes.fromString("foo")) +assert Hash.hash(globalInstance, Bytes.fromString("foo")) != + Hash.hash(globalInstance, Bytes.fromString("bar")) + +// hashInstance tests +let globalInstance1 = Hash.make() +let globalInstance2 = Hash.make() // uses a global seed generated at runtime, Hash.make is the same +assert Hash.hash(globalInstance1, 42) == Hash.hash(globalInstance1, 42) +let seededInstance1 = Hash.makeSeeded(1) +assert Hash.hash(seededInstance1, 32) == Hash.hash(seededInstance1, 32) +assert Hash.hash(seededInstance1, 32) != Hash.hash(seededInstance1, 31) +let seededInstance2 = Hash.makeSeeded(2) +assert Hash.hash(seededInstance1, 30) != Hash.hash(seededInstance2, 30) diff --git a/stdlib/hash.gr b/stdlib/hash.gr index 0ff4e8edd..463765f61 100644 --- a/stdlib/hash.gr +++ b/stdlib/hash.gr @@ -3,8 +3,12 @@ * * @example from "hash" include Hash * - * @example Hash.hash(1) - * @example Hash.hash("Hello World") + * @example + * let hashInstance = Hash.make() + * assert Hash.hash(hashInstance, "Hello World") == Hash.hash(hashInstance, "Hello World") + * @example + * let hashInstance = Hash.makeSeeded(10) + * assert Hash.hash(hashInstance, "Hello World") == Hash.hash(hashInstance, "Hello World") * * @since v0.1.0 */ @@ -34,7 +38,7 @@ from "runtime/unsafe/wasmi64" include WasmI64 from "runtime/unsafe/tags" include Tags from "runtime/dataStructures" include DataStructures -use DataStructures.{ tagSimpleNumber } +use DataStructures.{ tagSimpleNumber, untagSimpleNumber } from "runtime/numbers" include Numbers use Numbers.{ coerceNumberToWasmI32 } from "runtime/bigint" include Bigint as BI @@ -42,18 +46,6 @@ from "runtime/bigint" include Bigint as BI from "wasi/random" include Random from "result" include Result -@unsafe -let mut seed = 0n - -@unsafe -let initalize = () => { - // Delay initialization to the first call to `hash` to prevent WASI calls - // during startup - let random = Random.random() - seed = coerceNumberToWasmI32(Result.unwrap(random)) - seed -} - @unsafe let _MAX_HASH_DEPTH = 31n @@ -71,55 +63,53 @@ let m = 5n let n = 0xe6546b64n @unsafe -let mut h = seed - -@unsafe -let hash32 = k => { +let hash32 = (h, k) => { let mut k = k * c1 k = WasmI32.rotl(k, r1) k *= c2 - h = h ^ k - h = WasmI32.rotl(h, r2) - h = h * m + n + let h = h ^ k + let h = WasmI32.rotl(h, r2) + h * m + n } @unsafe -let hash64 = k => { +let hash64 = (h, k) => { use WasmI64.{ (>>>) } // convenience function for hashing 64-bit values - hash32(WasmI32.wrapI64(k)) - hash32(WasmI32.wrapI64(k >>> 32N)) + let h = hash32(h, WasmI32.wrapI64(k)) + let h = hash32(h, WasmI32.wrapI64(k >>> 32N)) + h } @unsafe -let hashRemaining = r => { +let hashRemaining = (h, r) => { // Note: wasm is little-endian so no swap is necessary let mut r = r * c1 r = WasmI32.rotl(r, r1) r *= c2 - h = h ^ r + h ^ r } @unsafe -let finalize = len => { - h = h ^ len +let finalize = (h, len) => { + let h = h ^ len - h = h ^ h >>> 16n - h *= 0x85ebca6bn - h = h ^ h >>> 13n - h *= 0xc2b2ae35n - h = h ^ h >>> 16n + let h = h ^ h >>> 16n + let h = h * 0x85ebca6bn + let h = h ^ h >>> 13n + let h = h * 0xc2b2ae35n + h ^ h >>> 16n } @unsafe -let rec hashOne = (val, depth) => { +let rec hashOne = (h, val, depth) => { if (depth > _MAX_HASH_DEPTH) { - void + h } else if ((val & Tags._GRAIN_NUMBER_TAG_MASK) != 0n) { - hash32(val) + hash32(h, val) } else if ( (val & Tags._GRAIN_GENERIC_TAG_MASK) == Tags._GRAIN_GENERIC_HEAP_TAG_TYPE @@ -130,141 +120,205 @@ let rec hashOne = (val, depth) => { let length = WasmI32.load(heapPtr, 4n) let extra = length % 4n let l = length - extra + let mut h = h for (let mut i = 0n; i < l; i += 4n) { - hash32(WasmI32.load(heapPtr + i, 8n)) + h = hash32(h, WasmI32.load(heapPtr + i, 8n)) } let mut rem = 0n for (let mut i = 0n; i < extra; i += 1n) { rem = rem << 8n rem = rem | WasmI32.load8U(heapPtr + l + i, 8n) } - if (rem != 0n) hashRemaining(rem) - finalize(length) + if (rem != 0n) h = hashRemaining(h, rem) + finalize(h, length) }, t when t == Tags._GRAIN_ADT_HEAP_TAG => { // moduleId - hash32(WasmI32.load(heapPtr, 4n)) + let h = hash32(h, WasmI32.load(heapPtr, 4n)) // typeId - hash32(WasmI32.load(heapPtr, 8n)) + let h = hash32(h, WasmI32.load(heapPtr, 8n)) // variantId - hash32(WasmI32.load(heapPtr, 12n)) + let h = hash32(h, WasmI32.load(heapPtr, 12n)) let arity = WasmI32.load(heapPtr, 16n) let a = arity * 4n + let mut h = h for (let mut i = 0n; i < a; i += 4n) { - hashOne(WasmI32.load(heapPtr + i, 20n), depth + 1n) + h = hashOne(h, WasmI32.load(heapPtr + i, 20n), depth + 1n) } - finalize(arity) + finalize(h, arity) }, t when t == Tags._GRAIN_RECORD_HEAP_TAG => { // moduleId - hash32(WasmI32.load(heapPtr, 4n)) + let h = hash32(h, WasmI32.load(heapPtr, 4n)) // typeId - hash32(WasmI32.load(heapPtr, 8n)) + let h = hash32(h, WasmI32.load(heapPtr, 8n)) let arity = WasmI32.load(heapPtr, 12n) let a = arity * 4n + let mut h = h for (let mut i = 0n; i < a; i += 4n) { - hashOne(WasmI32.load(heapPtr + i, 16n), depth + 1n) + h = hashOne(h, WasmI32.load(heapPtr + i, 16n), depth + 1n) } - finalize(arity) + finalize(h, arity) }, t when t == Tags._GRAIN_ARRAY_HEAP_TAG => { let arity = WasmI32.load(heapPtr, 4n) let a = arity * 4n + let mut h = h for (let mut i = 0n; i < a; i += 4n) { - hashOne(WasmI32.load(heapPtr + i, 8n), depth + 1n) + h = hashOne(h, WasmI32.load(heapPtr + i, 8n), depth + 1n) } - finalize(arity) + finalize(h, arity) }, t when t == Tags._GRAIN_TUPLE_HEAP_TAG => { let tupleLength = WasmI32.load(heapPtr, 4n) let l = tupleLength * 4n + let mut h = h for (let mut i = 0n; i < l; i += 4n) { - hashOne(WasmI32.load(heapPtr + i, 8n), depth + 1n) + h = hashOne(h, WasmI32.load(heapPtr + i, 8n), depth + 1n) } - finalize(tupleLength) + finalize(h, tupleLength) }, t when t == Tags._GRAIN_LAMBDA_HEAP_TAG => { - hash32(heapPtr) + hash32(h, heapPtr) }, t when t == Tags._GRAIN_BOXED_NUM_HEAP_TAG => { let tag = WasmI32.load(heapPtr, 4n) match (tag) { t when t == Tags._GRAIN_INT64_BOXED_NUM_TAG => { - hash32(WasmI32.load(heapPtr, 8n)) - hash32(WasmI32.load(heapPtr, 12n)) + let h = hash32(h, WasmI32.load(heapPtr, 8n)) + hash32(h, WasmI32.load(heapPtr, 12n)) }, t when t == Tags._GRAIN_BIGINT_BOXED_NUM_TAG => { // TODO(#1187): should include fixint size once implemented let size = BI.getSize(heapPtr) - hash32(size) - hash32(BI.getFlags(heapPtr)) + let h = hash32(h, size) + let mut h = hash32(h, BI.getFlags(heapPtr)) for (let mut i = 0n; i < size; i += 1n) { - hash64(BI.getLimb(heapPtr, i)) + h = hash64(h, BI.getLimb(heapPtr, i)) } + h }, t when t == Tags._GRAIN_FLOAT64_BOXED_NUM_TAG => { - hash32(WasmI32.load(heapPtr, 8n)) - hash32(WasmI32.load(heapPtr, 12n)) + let h = hash32(h, WasmI32.load(heapPtr, 8n)) + let h = hash32(h, WasmI32.load(heapPtr, 12n)) + h }, t when t == Tags._GRAIN_RATIONAL_BOXED_NUM_TAG => { - hashOne(WasmI32.load(heapPtr, 8n), depth + 1n) - hashOne(WasmI32.load(heapPtr, 12n), depth + 1n) + let h = hashOne(h, WasmI32.load(heapPtr, 8n), depth + 1n) + let h = hashOne(h, WasmI32.load(heapPtr, 12n), depth + 1n) + h }, _ => { - hash32(heapPtr) + hash32(h, heapPtr) }, } }, t when t == Tags._GRAIN_INT32_HEAP_TAG || t == Tags._GRAIN_FLOAT32_HEAP_TAG || t == Tags._GRAIN_UINT32_HEAP_TAG => { - hash32(WasmI32.load(heapPtr, 4n)) + hash32(h, WasmI32.load(heapPtr, 4n)) }, t when t == Tags._GRAIN_UINT64_HEAP_TAG => { - hash32(WasmI32.load(heapPtr, 8n)) - hash32(WasmI32.load(heapPtr, 12n)) + let h = hash32(h, WasmI32.load(heapPtr, 8n)) + let h = hash32(h, WasmI32.load(heapPtr, 12n)) + h }, _ => { - hash32(heapPtr) + hash32(h, heapPtr) }, } } else { // Handle non-heap values: booleans, chars, void, etc. - hash32(val) + hash32(h, val) + } +} + +/** + * Represents a particular hashing instance. + * + * @since v0.7.0 + */ +abstract type HashInstance = Number + +let mut seed: HashInstance = 0 +@unsafe +let getSeed = () => { + if (WasmI32.eqz(untagSimpleNumber(seed))) { + // Delay initialization to the first call to `hash` to prevent WASI calls + // during startup + let random = Random.random() + seed = Result.unwrap(random) } + seed: HashInstance } /** - * A generic hash function that produces an integer from any value. If `a == b` then `Hash.hash(a) == Hash.hash(b)`. + * Produces a generic hash instance using a random seed value. + * + * @returns A hashing instance that can be consumed during hashing + * + * @throws Failure(String): If WASI random_get fails + * + * @example + * let hashInstance = Hash.make() + * assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World) * + * @since v0.7.0 + */ +provide let make = () => { + let seed = getSeed() + seed +} + +/** + * Produces a hashInstance using the given seed. + * + * @param seed: The seed to use while hashing + * @returns A hashing instance that can be consumed during hashing + * + * @example + * let hashInstance = Hash.makeSeeded(1) + * assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World) + * @example + * let hashInstance1 = Hash.makeSeeded(1) + * let hashInstance2 = Hash.makeSeeded(2) + * assert Hash.hash(hashInstance1," Hello World") != Hash.hash(hashInstance2, "Hello World) + * + * @since v0.7.0 + */ +@unsafe +provide let makeSeeded = (seed: Number) => { + return seed: HashInstance +} + +/** + * A generic hash function that produces an integer from any value given a hashing instance. If `a == b` then `Hash.hash(h, a) == Hash.hash(h, b)`. + * + * @param hashInstance: The hashing instance to use as a seed * @param anything: The value to hash + * * @returns A hash for the given value - * - * @throws Failure(String): If WASI random_get fails * - * @example assert Hash.hash(1) == Hash.hash(1) - * @example assert Hash.hash("Hello World") == Hash.hash("Hello World") + * @example + * let hashInstance = Hash.makeSeeded(1) + * assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World) * * @since v0.1.0 + * @history v0.7.0: Added `hashInstance` parameter instead of using a global seed */ @unsafe -provide let hash = anything => { - h = if (WasmI32.eqz(seed)) { - initalize() - } else { - seed - } - - hashOne(WasmI32.fromGrain(anything), 0n) +provide let hash = (hashInstance: HashInstance, anything) => { + let h = coerceNumberToWasmI32(hashInstance) + let h = hashOne(h, WasmI32.fromGrain(anything), 0n) ignore(anything) - finalize(0n) + let h = finalize(h, 0n) // Tag the number on the way out. // Since Grain has proper modulus, negative numbers are okay. diff --git a/stdlib/hash.md b/stdlib/hash.md index aeab5ca40..fb41b0a82 100644 --- a/stdlib/hash.md +++ b/stdlib/hash.md @@ -14,55 +14,143 @@ from "hash" include Hash ``` ```grain -Hash.hash(1) +let hashInstance = Hash.make() +assert Hash.hash(hashInstance, "Hello World") == Hash.hash(hashInstance, "Hello World") ``` ```grain -Hash.hash("Hello World") +let hashInstance = Hash.makeSeeded(10) +assert Hash.hash(hashInstance, "Hello World") == Hash.hash(hashInstance, "Hello World") ``` +## Types + +Type declarations included in the Hash module. + +### Hash.**HashInstance** + +
+Added in next +No other changes yet. +
+ +```grain +type HashInstance +``` + +Represents a particular hashing instance. + ## Values Functions and constants included in the Hash module. -### Hash.**hash** +### Hash.**make**
-Added in 0.1.0 +Added in next No other changes yet.
```grain -hash : (anything: a) => Number +make : () => HashInstance +``` + +Produces a generic hash instance using a random seed value. + +Returns: + +|type|description| +|----|-----------| +|`HashInstance`|A hashing instance that can be consumed during hashing| + +Throws: + +`Failure(String)` + +* If WASI random_get fails + +Examples: + +```grain +let hashInstance = Hash.make() +assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World) ``` -A generic hash function that produces an integer from any value. If `a == b` then `Hash.hash(a) == Hash.hash(b)`. +### Hash.**makeSeeded** + +
+Added in next +No other changes yet. +
+ +```grain +makeSeeded : (seed: Number) => HashInstance +``` + +Produces a hashInstance using the given seed. Parameters: |param|type|description| |-----|----|-----------| -|`anything`|`a`|The value to hash| +|`seed`|`Number`|The seed to use while hashing| Returns: |type|description| |----|-----------| -|`Number`|A hash for the given value| +|`HashInstance`|A hashing instance that can be consumed during hashing| -Throws: +Examples: -`Failure(String)` +```grain +let hashInstance = Hash.makeSeeded(1) +assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World) +``` -* If WASI random_get fails +```grain +let hashInstance1 = Hash.makeSeeded(1) +let hashInstance2 = Hash.makeSeeded(2) +assert Hash.hash(hashInstance1," Hello World") != Hash.hash(hashInstance2, "Hello World) +``` -Examples: +### Hash.**hash** + +
+Added in 0.1.0 + + + + + + + +
versionchanges
nextAdded `hashInstance` parameter instead of using a global seed
+
```grain -assert Hash.hash(1) == Hash.hash(1) +hash : (hashInstance: HashInstance, anything: a) => Number ``` +A generic hash function that produces an integer from any value given a hashing instance. If `a == b` then `Hash.hash(h, a) == Hash.hash(h, b)`. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`hashInstance`|`HashInstance`|The hashing instance to use as a seed| +|`anything`|`a`|The value to hash| + +Returns: + +|type|description| +|----|-----------| +|`Number`|A hash for the given value| + +Examples: + ```grain -assert Hash.hash("Hello World") == Hash.hash("Hello World") +let hashInstance = Hash.makeSeeded(1) +assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World) ``` diff --git a/stdlib/map.gr b/stdlib/map.gr index 4ec222da9..cf771ff99 100644 --- a/stdlib/map.gr +++ b/stdlib/map.gr @@ -14,7 +14,6 @@ from "option" include Option from "runtime/dataStructures" include DataStructures use DataStructures.{ allocateArray, untagSimpleNumber } from "hash" include Hash -use Hash.{ hash } from "runtime/unsafe/memory" include Memory from "runtime/unsafe/wasmi32" include WasmI32 @@ -27,6 +26,7 @@ record rec Bucket { abstract record Map { mut size: Number, + hashInstance: Hash.HashInstance, mut buckets: Array>>, } @@ -47,25 +47,28 @@ provide record InternalMapStats { * @param size: The initial storage size of the map * @returns An empty map with the given initial storage size * + * @throws Failure(String): If WASI random_get fails + * * @since v0.2.0 * @history v0.6.0: Merged with `makeSized`; modified signature to accept size */ provide let make = (size=16) => { // TODO: This could take an `eq` function to custom comparisons let buckets = Array.make(size, None) - { size: 0, buckets } + let hashInstance = Hash.make() + { size: 0, hashInstance, buckets } } -let getBucketIndex = (key, buckets) => { +let getBucketIndex = (hashInstance, key, buckets) => { let bucketsLength = Array.length(buckets) - let hashedKey = hash(key) + let hashedKey = Hash.hash(hashInstance, key) hashedKey % bucketsLength } -let rec copyNodeWithNewHash = (oldNode, next, tail) => { +let rec copyNodeWithNewHash = (hashInstance, oldNode, next, tail) => { match (oldNode) { None => void, Some(node) => { - let idx = getBucketIndex(node.key, next) + let idx = getBucketIndex(hashInstance, node.key, next) let newNode = Some(node) match (tail[idx]) { None => { @@ -79,7 +82,7 @@ let rec copyNodeWithNewHash = (oldNode, next, tail) => { // Always place this node as the new tail tail[idx] = newNode // Recurse with the next node - copyNodeWithNewHash(node.next, next, tail) + copyNodeWithNewHash(hashInstance, node.next, next, tail) }, } } @@ -93,8 +96,9 @@ let resize = map => { // This tracks the tail nodes so we can set their `next` to None let tailNodes = Array.make(nextSize, None) map.buckets = nextBuckets + let hashInstance = map.hashInstance Array.forEach(old => { - copyNodeWithNewHash(old, nextBuckets, tailNodes) + copyNodeWithNewHash(hashInstance, old, nextBuckets, tailNodes) }, currentBuckets) Array.forEach(tail => { match (tail) { @@ -132,7 +136,8 @@ let rec replaceInBucket = (key, value, node) => { */ provide let set = (key, value, map) => { let buckets = map.buckets - let idx = getBucketIndex(key, buckets) + let hashInstance = map.hashInstance + let idx = getBucketIndex(hashInstance, key, buckets) let bucket = buckets[idx] match (bucket) { None => { @@ -176,7 +181,8 @@ let rec valueFromBucket = (key, node) => { */ provide let get = (key, map) => { let buckets = map.buckets - let idx = getBucketIndex(key, buckets) + let hashInstance = map.hashInstance + let idx = getBucketIndex(hashInstance, key, buckets) let bucket = buckets[idx] match (bucket) { None => None, @@ -206,7 +212,8 @@ let rec nodeInBucket = (key, node) => { */ provide let contains = (key, map) => { let buckets = map.buckets - let idx = getBucketIndex(key, buckets) + let hashInstance = map.hashInstance + let idx = getBucketIndex(hashInstance, key, buckets) let bucket = buckets[idx] match (bucket) { None => false, @@ -238,7 +245,8 @@ let rec removeInBucket = (key, node) => { */ provide let remove = (key, map) => { let buckets = map.buckets - let idx = getBucketIndex(key, buckets) + let hashInstance = map.hashInstance + let idx = getBucketIndex(hashInstance, key, buckets) let bucket = buckets[idx] match (bucket) { None => void, diff --git a/stdlib/map.md b/stdlib/map.md index 88c16eb46..55d6f9cf1 100644 --- a/stdlib/map.md +++ b/stdlib/map.md @@ -75,6 +75,12 @@ Returns: |----|-----------| |`Map`|An empty map with the given initial storage size| +Throws: + +`Failure(String)` + +* If WASI random_get fails + ### Map.**set**
diff --git a/stdlib/set.gr b/stdlib/set.gr index e7379531a..f380d514d 100644 --- a/stdlib/set.gr +++ b/stdlib/set.gr @@ -11,7 +11,6 @@ module Set from "list" include List from "array" include Array from "hash" include Hash -use Hash.{ hash } record rec Bucket { mut key: t, @@ -20,6 +19,7 @@ record rec Bucket { abstract record Set { mut size: Number, + hashInstance: Hash.HashInstance, mut buckets: Array>>, } @@ -41,25 +41,28 @@ provide record InternalSetStats { * @param size: The initial storage size of the set * @returns An empty set with the given initial storage size * + * @throws Failure(String): If WASI random_get fails + * * @since v0.3.0 * @history v0.6.0: Merged with `makeSized`; modified signature to accept size */ provide let make = (size=16) => { let buckets = Array.make(size, None) - { size: 0, buckets } + let hashInstance = Hash.make() + { size: 0, hashInstance, buckets } } -let getBucketIndex = (key, buckets) => { +let getBucketIndex = (hashInstance, key, buckets) => { let bucketsLength = Array.length(buckets) - let hashedKey = hash(key) + let hashedKey = Hash.hash(hashInstance, key) hashedKey % bucketsLength } -let rec copyNodeWithNewHash = (oldNode, next, tail) => { +let rec copyNodeWithNewHash = (hashInstance, oldNode, next, tail) => { match (oldNode) { None => void, Some(node) => { - let idx = getBucketIndex(node.key, next) + let idx = getBucketIndex(hashInstance, node.key, next) let newNode = Some(node) match (tail[idx]) { None => { @@ -73,7 +76,7 @@ let rec copyNodeWithNewHash = (oldNode, next, tail) => { // Always place this node as the new tail tail[idx] = newNode // Recurse with the next node - copyNodeWithNewHash(node.next, next, tail) + copyNodeWithNewHash(hashInstance, node.next, next, tail) }, } } @@ -87,8 +90,9 @@ let resize = set => { // This tracks the tail nodes so we can set their `next` to None let tailNodes = Array.make(nextSize, None) set.buckets = nextBuckets + let hashInstance = set.hashInstance Array.forEach(old => { - copyNodeWithNewHash(old, nextBuckets, tailNodes) + copyNodeWithNewHash(hashInstance, old, nextBuckets, tailNodes) }, currentBuckets) Array.forEach(tail => { match (tail) { @@ -124,7 +128,8 @@ let rec nodeInBucket = (key, node) => { */ provide let add = (key, set) => { let buckets = set.buckets - let idx = getBucketIndex(key, buckets) + let hashInstance = set.hashInstance + let idx = getBucketIndex(hashInstance, key, buckets) let bucket = buckets[idx] match (bucket) { None => { @@ -157,7 +162,8 @@ provide let add = (key, set) => { */ provide let contains = (key, set) => { let buckets = set.buckets - let idx = getBucketIndex(key, buckets) + let hashInstance = set.hashInstance + let idx = getBucketIndex(hashInstance, key, buckets) let bucket = buckets[idx] match (bucket) { None => false, @@ -189,7 +195,8 @@ let rec removeInBucket = (key, node) => { */ provide let remove = (key, set) => { let buckets = set.buckets - let idx = getBucketIndex(key, buckets) + let hashInstance = set.hashInstance + let idx = getBucketIndex(hashInstance, key, buckets) let bucket = buckets[idx] match (bucket) { None => void, diff --git a/stdlib/set.md b/stdlib/set.md index 7370fb02d..244ea1023 100644 --- a/stdlib/set.md +++ b/stdlib/set.md @@ -75,6 +75,12 @@ Returns: |----|-----------| |`Set`|An empty set with the given initial storage size| +Throws: + +`Failure(String)` + +* If WASI random_get fails + ### Set.**add**