Skip to content

Instantly share code, notes, and snippets.

@jfet97
Last active November 4, 2023 18:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jfet97/5cc779a276b900ac3a6145a84ecc50b0 to your computer and use it in GitHub Desktop.
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.
// 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