Last active
November 4, 2023 18:11
-
-
Save jfet97/5cc779a276b900ac3a6145a84ecc50b0 to your computer and use it in GitHub Desktop.
Once you prove that a key is present, you can use it `undefined`-free in any number of other operations.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://hackage.haskell.org/package/justified-containers-0.3.0.0/docs/src/Data-Map-Justified.html | |
// TODO: not all operations have been translated into TS | |
// TODO: is it possible to avoid continuations? Don't think so, TS doesn't support existentials | |
export type JMap<PH, K, V> = { readonly carrier: ReadonlyMap<K, V>, readonly proof: PH, readonly brand: unique symbol } | |
export type JKey<PH, K> = { readonly carrier: K, readonly proof: PH, readonly brand: unique symbol } | |
// internals | |
function castMap<K, V, PH = any>(map: ReadonlyMap<K, V>): JMap<PH, K, V> { | |
return map as unknown as JMap<any, K, V> | |
} | |
function castKey<K, PH = any>(key: K): JKey<PH, K> { | |
return key as JKey<any, K> | |
} | |
function qed<PH, PH_, K>(jkey: JKey<PH, K>): JKey<PH_, K> { | |
return jkey as unknown as JKey<PH_, K> | |
} | |
// module functions | |
export function theMap<K, V>(jmap: JMap<any, K, V>): ReadonlyMap<K, V> { | |
return jmap as unknown as ReadonlyMap<K, V> | |
} | |
export function theKey<K>(jkey: JKey<any, K>): K { | |
return jkey as unknown as K | |
} | |
export function withMap<K, V, T>(map: ReadonlyMap<K, V>, cont: <PH>(jmap: JMap<PH, K, V>) => T): T { | |
return cont(castMap(map)) | |
} | |
export function withMap2<K1, V1, K2, V2, T>( | |
map1: ReadonlyMap<K1, V1>, | |
map2: ReadonlyMap<K2, V2>, | |
cont: <PH1, PH2>(jmap1: JMap<PH1, K1, V1>, jmap2: JMap<PH2, K2, V2>) => T | |
): T { | |
return cont(castMap(map1), castMap(map2)) | |
} | |
export function withMap3<K1, V1, K2, V2, K3, V3, T>( | |
map1: ReadonlyMap<K1, V1>, | |
map2: ReadonlyMap<K2, V2>, | |
map3: ReadonlyMap<K3, V3>, | |
cont: <PH1, PH2, PH3>(jmap1: JMap<PH1, K1, V1>, jmap2: JMap<PH2, K2, V2>, jmap3: JMap<PH3, K3, V3>) => T | |
): T { | |
return cont(castMap(map1), castMap(map2), castMap(map3)) | |
} | |
export function withSingleton<K, V, T>(key: K, value: V, cont: <PH>(jkey: JKey<PH, K>, jmap: JMap<PH, K, V>) => T): T { | |
return cont(castKey(key), castMap(new Map([[key, value]]))) | |
} | |
export function fromIterable<K, V, T>(iterable: Iterable<[K, V]>, cont: <PH>(jmap: JMap<PH, K, V>) => T): T { | |
return withMap(new Map(iterable), cont) | |
} | |
export function lookup<PH, K, V>(jkey: JKey<PH, K>, jmap: JMap<PH, K, V>): V { | |
const toRet = theMap(jmap).get(theKey(jkey)) | |
if (typeof toRet === "undefined") { | |
throw new Error("Uh oh, it seems that the proof of the key is wrong. This should never happen!") | |
} else { | |
return toRet | |
} | |
} | |
export function getJKeys<PH, K, V>(jmap: JMap<PH, K, V>): JKey<PH, K>[] { | |
return [...theMap(jmap).keys()].map(castKey<K, PH>) | |
} | |
export function member<PH, K, V>(key: K, jmap: JMap<PH, K, V>): JKey<PH, K> | null { | |
if (theMap(jmap).has(key)) { | |
return castKey(key) | |
} else { | |
return null | |
} | |
} | |
export function updateValue<PH, K, V>(cb: (v: V) => V, jkey: JKey<PH, K>, jmap: JMap<PH, K, V>): JMap<PH, K, V> { | |
const v_ = cb(lookup(jkey, jmap)); | |
return castMap(new Map( | |
[...theMap(jmap).entries()] | |
.filter(([k, _]) => k !== jkey) | |
.concat([[theKey(jkey), v_]]) | |
)) | |
} | |
export function reinsertValue<PH, K, V>(value: V, jkey: JKey<PH, K>, jmap: JMap<PH, K, V>): JMap<PH, K, V> { | |
return updateValue(() => value, jkey, jmap) | |
} | |
export function insertValue<PH, K, V, T>( | |
value: V, | |
key: K, | |
jmap: JMap<PH, K, V>, | |
cont: <PH_>(jkey_: JKey<PH_, K>, convertJKey: (jkey: JKey<PH, K>) => JKey<PH_, K>, jmap_: JMap<PH_, K, V>) => T | |
): T { | |
return cont(castKey(key), qed, castMap(new Map([...theMap(jmap).entries(), [key, value]]))) | |
} | |
export function deleteJKey<PH, K, V, T>( | |
key: JKey<PH, K>, | |
jmap: JMap<PH, K, V>, | |
cont: <PH_>(convertJKey: (jkey_: JKey<PH_, K>) => JKey<PH, K>, jmap_: JMap<PH_, K, V>) => T | |
): T{ | |
return cont(qed, castMap(new Map([...theMap(jmap).entries()].filter(([k, _]) => k !== key)))) | |
} | |
export function sameJKeys<PH1, PH2, K, V1, V2>(jmap1: JMap<PH1, K, V1>, jmap2: JMap<PH2, K, V2>): { | |
(jkey: JKey<PH1, K>): JKey<PH2, K>, | |
(jkey: JKey<PH2, K>): JKey<PH1, K>, | |
} | null { | |
if(getJKeys(jmap1).every(jkey => theMap(jmap2).has(theKey(jkey)))) { | |
return qed | |
} else { | |
return null | |
} | |
} | |
export function mergeProofs<PH1, PH2, K, V1, V2, T, U>( | |
jmap1: JMap<PH1, K, V1>, | |
jmap2: JMap<PH2, K, V2>, | |
contSuccess: <PH12>(jmap1: JMap<PH12, K, V1>, jmap2: JMap<PH12, K, V2>, convertJKey: { | |
(jkey1: JKey<PH1, K>): JKey<PH12, K> | |
(jkey2: JKey<PH2, K>): JKey<PH12, K> | |
}) => T, | |
contFail: () => U | |
): T | U { | |
if(sameJKeys(jmap1, jmap2)) { | |
return contSuccess(jmap1, jmap2, qed) | |
} else { | |
return contFail() | |
} | |
} | |
export function mapWithJKey<PH, K, A, B>(proj: (jkey: JKey<PH, K>, a: A) => B, jmap: JMap<PH, K, A>): JMap<PH, K, B> { | |
return castMap(new Map([...theMap(jmap).entries()].map(([k, v]) => [k, proj(castKey(k), v)]))) | |
} | |
export function zipWith<PH, K, A, B, C>(fn: (a: A, b: B) => C, jmapa: JMap<PH, K, A>, jmapb: JMap<PH, K, B>): JMap<PH, K, C> { | |
return mapWithJKey((jkey, a) => fn(a, lookup(jkey, jmapb)), jmapa) | |
} | |
// usage | |
withMap(new Map([["key", "value"]]), (jmap) => { | |
const jkeyWrong = member("yek", jmap) | |
const jkeyRight = member("key", jmap) | |
if(jkeyWrong) { | |
console.log("This should never happen!") | |
console.log(lookup(jkeyWrong, jmap)) | |
} | |
if(jkeyRight) { | |
console.log("This should always happen!") | |
console.log(lookup(jkeyRight, jmap)) | |
} | |
}) | |
fromIterable([["ciao", 123], [12, true], [{}, "false"]] as [string | number | {}, number | boolean | string][], (jmap) => { | |
const jkeys = getJKeys(jmap) | |
const v1 = lookup(jkeys[0], jmap) | |
const v2 = lookup(jkeys[1], jmap) | |
const v3 = lookup(jkeys[2], jmap) | |
console.log({ v1, v2, v3 }) | |
}) | |
withSingleton("ciao", 123, (ciao, jmap) => { | |
const num = lookup(ciao, jmap) | |
return insertValue(num + 1, "ciao2", jmap, (ciao2, qed, jmap_) => { | |
const numAgain = lookup(qed(ciao), jmap_) | |
const numPlus1 = lookup(ciao2, jmap_) | |
return deleteJKey(ciao2, jmap_, (deq, jmap__) => { | |
const ciao = deq(getJKeys(jmap__)[0]) | |
const numAgainAgain = lookup(ciao, jmap_) | |
console.log({ num, numAgain, numPlus1, numAgainAgain }) | |
return "done" | |
}) | |
}) | |
}) | |
withMap2(new Map([["boh", 123]]), new Map([["boh", "123"]]), (jmap1, jmap2) => { | |
const sameKeysProof = sameJKeys(jmap1, jmap2) | |
const key1 = getJKeys(jmap1)[0] | |
const key2 = getJKeys(jmap2)[0] | |
if(sameKeysProof) { | |
console.log(lookup(sameKeysProof(key1), jmap2)) | |
console.log(lookup(sameKeysProof(key2), jmap1)) | |
} else { | |
console.log("nope") | |
} | |
}) | |
withMap2(new Map([["boh", 123]]), new Map([["boh", "123"]]), (jmap1, jmap2) => { | |
const key1 = getJKeys(jmap1)[0] | |
const key2 = getJKeys(jmap2)[0] | |
return mergeProofs(jmap1, jmap2, (jmap1, jmap2, convertJKey) => { | |
console.log(lookup(convertJKey(key1), jmap2)) | |
console.log(lookup(convertJKey(key2), jmap1)) | |
const zipped = zipWith((a, b) => `a: ${a}; b: ${b}`, jmap1, jmap2) | |
console.log(lookup(convertJKey(key1), zipped)) | |
console.log(lookup(convertJKey(key2), zipped)) | |
}, | |
() => {}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment