201 lines
4.1 KiB
TypeScript
201 lines
4.1 KiB
TypeScript
/**
|
|
* @since 2.0.0
|
|
*/
|
|
import { pipe } from "./Function.js"
|
|
import { globalValue } from "./GlobalValue.js"
|
|
import { hasProperty } from "./Predicate.js"
|
|
import { structuralRegionState } from "./Utils.js"
|
|
|
|
/** @internal */
|
|
const randomHashCache = globalValue(
|
|
Symbol.for("effect/Hash/randomHashCache"),
|
|
() => new WeakMap<object, number>()
|
|
)
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category symbols
|
|
*/
|
|
export const symbol: unique symbol = Symbol.for("effect/Hash")
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category models
|
|
*/
|
|
export interface Hash {
|
|
[symbol](): number
|
|
}
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
export const hash: <A>(self: A) => number = <A>(self: A) => {
|
|
if (structuralRegionState.enabled === true) {
|
|
return 0
|
|
}
|
|
|
|
switch (typeof self) {
|
|
case "number":
|
|
return number(self)
|
|
case "bigint":
|
|
return string(self.toString(10))
|
|
case "boolean":
|
|
return string(String(self))
|
|
case "symbol":
|
|
return string(String(self))
|
|
case "string":
|
|
return string(self)
|
|
case "undefined":
|
|
return string("undefined")
|
|
case "function":
|
|
case "object": {
|
|
if (self === null) {
|
|
return string("null")
|
|
} else if (self instanceof Date) {
|
|
return hash(self.toISOString())
|
|
} else if (self instanceof URL) {
|
|
return hash(self.href)
|
|
} else if (isHash(self)) {
|
|
return self[symbol]()
|
|
} else {
|
|
return random(self)
|
|
}
|
|
}
|
|
default:
|
|
throw new Error(
|
|
`BUG: unhandled typeof ${typeof self} - please report an issue at https://github.com/Effect-TS/effect/issues`
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
export const random: <A extends object>(self: A) => number = (self) => {
|
|
if (!randomHashCache.has(self)) {
|
|
randomHashCache.set(self, number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)))
|
|
}
|
|
return randomHashCache.get(self)!
|
|
}
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
export const combine: (b: number) => (self: number) => number = (b) => (self) => (self * 53) ^ b
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
export const optimize = (n: number): number => (n & 0xbfffffff) | ((n >>> 1) & 0x40000000)
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category guards
|
|
*/
|
|
export const isHash = (u: unknown): u is Hash => hasProperty(u, symbol)
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
export const number = (n: number) => {
|
|
if (n !== n || n === Infinity) {
|
|
return 0
|
|
}
|
|
let h = n | 0
|
|
if (h !== n) {
|
|
h ^= n * 0xffffffff
|
|
}
|
|
while (n > 0xffffffff) {
|
|
h ^= n /= 0xffffffff
|
|
}
|
|
return optimize(h)
|
|
}
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
export const string = (str: string) => {
|
|
let h = 5381, i = str.length
|
|
while (i) {
|
|
h = (h * 33) ^ str.charCodeAt(--i)
|
|
}
|
|
return optimize(h)
|
|
}
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
export const structureKeys = <A extends object>(o: A, keys: ReadonlyArray<keyof A>) => {
|
|
let h = 12289
|
|
for (let i = 0; i < keys.length; i++) {
|
|
h ^= pipe(string(keys[i]! as string), combine(hash((o as any)[keys[i]!])))
|
|
}
|
|
return optimize(h)
|
|
}
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
export const structure = <A extends object>(o: A) =>
|
|
structureKeys(o, Object.keys(o) as unknown as ReadonlyArray<keyof A>)
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
export const array = <A>(arr: ReadonlyArray<A>) => {
|
|
let h = 6151
|
|
for (let i = 0; i < arr.length; i++) {
|
|
h = pipe(h, combine(hash(arr[i])))
|
|
}
|
|
return optimize(h)
|
|
}
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
export const cached: {
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
(self: object): (hash: number) => number
|
|
/**
|
|
* @since 2.0.0
|
|
* @category hashing
|
|
*/
|
|
(self: object, hash: number): number
|
|
} = function() {
|
|
if (arguments.length === 1) {
|
|
const self = arguments[0] as object
|
|
return function(hash: number) {
|
|
Object.defineProperty(self, symbol, {
|
|
value() {
|
|
return hash
|
|
},
|
|
enumerable: false
|
|
})
|
|
return hash
|
|
} as any
|
|
}
|
|
const self = arguments[0] as object
|
|
const hash = arguments[1] as number
|
|
Object.defineProperty(self, symbol, {
|
|
value() {
|
|
return hash
|
|
},
|
|
enumerable: false
|
|
})
|
|
|
|
return hash
|
|
}
|