aboutsummaryrefslogtreecommitdiff
path: root/stdlib/source/lux/data/struct/dict.lux
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--stdlib/source/lux/data/struct/dict.lux675
1 files changed, 675 insertions, 0 deletions
diff --git a/stdlib/source/lux/data/struct/dict.lux b/stdlib/source/lux/data/struct/dict.lux
new file mode 100644
index 000000000..a10e30dca
--- /dev/null
+++ b/stdlib/source/lux/data/struct/dict.lux
@@ -0,0 +1,675 @@
+## Copyright (c) Eduardo Julian. All rights reserved.
+## This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+## If a copy of the MPL was not distributed with this file,
+## You can obtain one at http://mozilla.org/MPL/2.0/.
+
+(;module:
+ lux
+ (lux (control hash
+ eq)
+ (data maybe
+ (struct [list "List/" Fold<List> Functor<List> Monoid<List>]
+ [array #+ Array "Array/" Functor<Array> Fold<Array>])
+ [bit]
+ [product]
+ text/format
+ [number])
+ ))
+
+## This implementation of Hash Array Mapped Trie (HAMT) is based on
+## Clojure's PersistentHashMap implementation.
+## That one is further based on Phil Bagwell's Hash Array Mapped Trie.
+
+## [Utils]
+## Bitmaps are used to figure out which branches on a #Base node are
+## populated. The number of bits that are 1s in a bitmap signal the
+## size of the #Base node.
+(type: BitMap Nat)
+
+## Represents the position of a node in a BitMap.
+## It's meant to be a single bit set on a 32-bit word.
+## The position of the bit reflects whether an entry in an analogous
+## position exists within a #Base, as reflected in it's BitMap.
+(type: BitPosition Nat)
+
+## An index into an array.
+(type: Index Nat)
+
+## A hash-code derived from a key during tree-traversal.
+(type: Hash-Code Nat)
+
+## Represents the nesting level of a leaf or node, when looking-it-up
+## while exploring the tree.
+## Changes in levels are done by right-shifting the hashes of keys by
+## the appropriate multiple of the branching-exponent.
+## A shift of 0 means root level.
+## A shift of (* branching-exponent 1) means level 2.
+## A shift of (* branching-exponent N) means level N+1.
+(type: Level Nat)
+
+## Nodes for the tree data-structure that organizes the data inside
+## Dicts.
+(type: (Node k v)
+ (#Hierarchy Nat (Array (Node k v)))
+ (#Base BitMap
+ (Array (Either (Node k v)
+ [k v])))
+ (#Collisions Hash-Code (Array [k v])))
+
+## #Hierarchy nodes are meant to point down only to lower-level nodes.
+(type: (Hierarchy k v)
+ [Nat (Array (Node k v))])
+
+## #Base nodes may point down to other nodes, but also to leaves,
+## which are KV pairs.
+(type: (Base k v)
+ (Array (Either (Node k v)
+ [k v])))
+
+## #Collisions are collections of KV-pairs for which the key is
+## different on each case, but their hashes are all the same (thus
+## causing a collision).
+(type: (Collisions k v)
+ (Array [k v]))
+
+## That bitmap for an empty #Base is 0.
+## Which is the same as 0000 0000 0000 0000 0000 0000 0000 0000.
+## Or 0x00000000.
+## Which is 32 zeroes, since the branching factor is 32.
+(def: clean-bitmap
+ BitMap
+ +0)
+
+## Bitmap position (while looking inside #Base nodes) is determined by
+## getting 5 bits from a hash of the key being looked up and using
+## them as an index into the array inside #Base.
+## Since the data-structure can have multiple levels (and the hash has
+## more than 5 bits), the binary-representation of the hash is shifted
+## by 5 positions on each step (2^5 = 32, which is the branching
+## factor).
+## The initial shifting level, though, is 0 (which corresponds to the
+## shift in the shallowest node on the tree, which is the root node).
+(def: root-level
+ Level
+ +0)
+
+## The exponent to which 2 must be elevated, to reach the branching
+## factor of the data-structure.
+(def: branching-exponent
+ Nat
+ +5)
+
+## The threshold on which #Hierarchy nodes are demoted to #Base nodes,
+## which is 1/4 of the branching factor (or a left-shift 2).
+(def: demotion-threshold
+ Nat
+ (bit;<< (-+ +2 branching-exponent) +1))
+
+## The threshold on which #Base nodes are promoted to #Hierarchy nodes,
+## which is 1/2 of the branching factor (or a left-shift 1).
+(def: promotion-threshold
+ Nat
+ (bit;<< (-+ +1 branching-exponent) +1))
+
+## The size of hierarchy-nodes, which is 2^(branching-exponent).
+(def: hierarchy-nodes-size
+ Nat
+ (bit;<< branching-exponent +1))
+
+## The cannonical empty node, which is just an empty #Base node.
+(def: empty
+ Node
+ (#Base clean-bitmap (array;new +0)))
+
+## Expands a copy of the array, to have 1 extra slot, which is used
+## for storing the value.
+(def: (insert! idx value old-array)
+ (All [a] (-> Index a (Array a) (Array a)))
+ (let [old-size (array;size old-array)]
+ (|> (: (Array ($ 0))
+ (array;new (inc+ old-size)))
+ (array;copy idx +0 old-array +0)
+ (array;put idx value)
+ (array;copy (-+ idx old-size) idx old-array (inc+ idx)))))
+
+## Creates a copy of an array with an index set to a particular value.
+(def: (update! idx value array)
+ (All [a] (-> Index a (Array a) (Array a)))
+ (|> array array;clone (array;put idx value)))
+
+## Creates a clone of the array, with an empty position at index.
+(def: (vacant! idx array)
+ (All [a] (-> Index (Array a) (Array a)))
+ (|> array array;clone (array;remove idx)))
+
+## Shrinks a copy of the array by removing the space at index.
+(def: (remove! idx array)
+ (All [a] (-> Index (Array a) (Array a)))
+ (let [new-size (dec+ (array;size array))]
+ (|> (array;new new-size)
+ (array;copy idx +0 array +0)
+ (array;copy (-+ idx new-size) (inc+ idx) array idx))))
+
+## Given a top-limit for indices, produces all indices in [0, R).
+(def: indices-for
+ (-> Nat (List Index))
+ (|>. dec+ (list;range+ +0)))
+
+## Increases the level-shift by the branching-exponent, to explore
+## levels further down the tree.
+(def: level-up
+ (-> Level Level)
+ (++ branching-exponent))
+
+(def: hierarchy-mask BitMap (dec+ hierarchy-nodes-size))
+
+## Gets the branching-factor sized section of the hash corresponding
+## to a particular level, and uses that as an index into the array.
+(def: (level-index level hash)
+ (-> Level Hash-Code Index)
+ (bit;& hierarchy-mask
+ (bit;>>> level hash)))
+
+## A mechanism to go from indices to bit-positions.
+(def: (->bit-position index)
+ (-> Index BitPosition)
+ (bit;<< index +1))
+
+## The bit-position within a base that a given hash-code would have.
+(def: (bit-position level hash)
+ (-> Level Hash-Code BitPosition)
+ (->bit-position (level-index level hash)))
+
+(def: (bit-position-is-set? bit bitmap)
+ (-> BitPosition BitMap Bool)
+ (not (=+ clean-bitmap (bit;& bit bitmap))))
+
+## Figures out whether a bitmap only contains a single bit-position.
+(def: only-bit-position?
+ (-> BitPosition BitMap Bool)
+ =+)
+
+(def: (set-bit-position bit bitmap)
+ (-> BitPosition BitMap BitMap)
+ (bit;| bit bitmap))
+
+(def: unset-bit-position
+ (-> BitPosition BitMap BitMap)
+ bit;^)
+
+## Figures out the size of a bitmap-indexed array by counting all the
+## 1s within the bitmap.
+(def: bitmap-size
+ (-> BitMap Nat)
+ bit;count)
+
+## A mask that, for a given bit position, only allows all the 1s prior
+## to it, which would indicate the bitmap-size (and, thus, index)
+## associated with it.
+(def: bit-position-mask
+ (-> BitPosition BitMap)
+ dec+)
+
+## The index on the base array, based on it's bit-position.
+(def: (base-index bit-position bitmap)
+ (-> BitPosition BitMap Index)
+ (bitmap-size (bit;& (bit-position-mask bit-position)
+ bitmap)))
+
+## Produces the index of a KV-pair within a #Collisions node.
+(def: (collision-index Hash<K> key colls)
+ (All [K V] (-> (Hash K) K (Collisions K V) (Maybe Index)))
+ (:: Monad<Maybe> map product;left
+ (array;find+ (lambda [idx [key' val']]
+ (:: Hash<K> = key key'))
+ colls)))
+
+## When #Hierarchy nodes grow too small, they're demoted to #Base
+## nodes to save space.
+(def: (demote-hierarchy except-idx [h-size h-array])
+ (All [k v] (-> Index (Hierarchy k v) [BitMap (Base k v)]))
+ (List/fold (lambda [idx (^@ node [bitmap base])]
+ (case (array;get idx h-array)
+ #;None node
+ (#;Some sub-node) (if (=+ except-idx idx)
+ node
+ [(set-bit-position (->bit-position idx) bitmap)
+ (array;put idx (#;Left sub-node) base)])
+ ))
+ [clean-bitmap
+ (: (Base ($ 0) ($ 1))
+ (array;new (dec+ h-size)))]
+ (list;indices (array;size h-array))))
+
+## When #Base nodes grow too large, they're promoted to #Hierarchy to
+## add some depth to the tree and help keep it's balance.
+(def: (promote-base put' Hash<K> level bitmap base)
+ (All [K V]
+ (-> (-> Level Hash-Code K V (Hash K) (Node K V) (Node K V))
+ (Hash K) Level
+ BitMap (Base K V)
+ (Array (Node K V))))
+ (product;right (List/fold (lambda [hierarchy-idx (^@ default [base-idx h-array])]
+ (if (bit-position-is-set? (->bit-position hierarchy-idx)
+ bitmap)
+ [(inc+ base-idx)
+ (case (array;get base-idx base)
+ (#;Some (#;Left sub-node))
+ (array;put hierarchy-idx sub-node h-array)
+
+ (#;Some (#;Right [key' val']))
+ (array;put hierarchy-idx
+ (put' (level-up level) (:: Hash<K> hash key') key' val' Hash<K> empty)
+ h-array)
+
+ #;None
+ (undefined))]
+ default))
+ [+0
+ (: (Array (Node ($ 0) ($ 1)))
+ (array;new hierarchy-nodes-size))]
+ (indices-for hierarchy-nodes-size))))
+
+## All empty nodes look the same (a #Base node with clean bitmap is
+## used).
+## So, this test is introduced to detect them.
+(def: (empty?' node)
+ (All [K V] (-> (Node K V) Bool))
+ (case node
+ (^~ (#Base ;;clean-bitmap _))
+ true
+
+ _
+ false))
+
+(def: (put' level hash key val Hash<K> node)
+ (All [K V] (-> Level Hash-Code K V (Hash K) (Node K V) (Node K V)))
+ (case node
+ ## For #Hierarchy nodes, I check whether I can add the element to
+ ## a sub-node. If impossible, I introduced a new singleton sub-node.
+ (#Hierarchy _size hierarchy)
+ (let [idx (level-index level hash)
+ [_size' sub-node] (: [Nat (Node ($ 0) ($ 1))]
+ (case (array;get idx hierarchy)
+ (#;Some sub-node)
+ [_size sub-node]
+
+ _
+ [(inc+ _size) empty]))]
+ (#Hierarchy _size'
+ (update! idx (put' (level-up level) hash key val Hash<K> sub-node)
+ hierarchy)))
+
+ ## For #Base nodes, I check if the corresponding BitPosition has
+ ## already been used.
+ (#Base bitmap base)
+ (let [bit (bit-position level hash)]
+ (if (bit-position-is-set? bit bitmap)
+ ## If so...
+ (let [idx (base-index bit bitmap)]
+ (case (array;get idx base)
+ #;None
+ (undefined)
+
+ ## If it's being used by a node, I add the KV to it.
+ (#;Some (#;Left sub-node))
+ (let [sub-node' (put' (level-up level) hash key val Hash<K> sub-node)]
+ (#Base bitmap (update! idx (#;Left sub-node') base)))
+
+ ## Otherwise, if it's being used by a KV, I compare the keys.
+ (#;Some (#;Right key' val'))
+ (if (:: Hash<K> = key key')
+ ## If the same key is found, I replace the value.
+ (#Base bitmap (update! idx (#;Right key val) base))
+ ## Otherwise, I compare the hashes of the keys.
+ (#Base bitmap (update! idx
+ (#;Left (let [hash' (:: Hash<K> hash key')]
+ (if (=+ hash hash')
+ ## If the hashes are
+ ## the same, a new
+ ## #Collisions node
+ ## is added.
+ (#Collisions hash (|> (: (Array [($ 0) ($ 1)])
+ (array;new +2))
+ (array;put +0 [key' val'])
+ (array;put +1 [key val])))
+ ## Otherwise, I can
+ ## just keep using
+ ## #Base nodes, so I
+ ## add both KV pairs
+ ## to the empty one.
+ (let [next-level (level-up level)]
+ (|> empty
+ (put' next-level hash' key' val' Hash<K>)
+ (put' next-level hash key val Hash<K>))))))
+ base)))))
+ ## However, if the BitPosition has not been used yet, I check
+ ## whether this #Base node is ready for a promotion.
+ (let [base-count (bitmap-size bitmap)]
+ (if (>=+ promotion-threshold base-count)
+ ## If so, I promote it to a #Hierarchy node, and add the new
+ ## KV-pair as a singleton node to it.
+ (#Hierarchy (inc+ base-count)
+ (|> (promote-base put' Hash<K> level bitmap base)
+ (array;put (level-index level hash)
+ (put' (level-up level) hash key val Hash<K> empty))))
+ ## Otherwise, I just resize the #Base node to accommodate the
+ ## new KV-pair.
+ (#Base (set-bit-position bit bitmap)
+ (insert! (base-index bit bitmap) (#;Right [key val]) base))))))
+
+ ## For #Collisions nodes, I compare the hashes.
+ (#Collisions _hash _colls)
+ (if (=+ hash _hash)
+ ## If they're equal, that means the new KV contributes to the
+ ## collisions.
+ (case (collision-index Hash<K> key _colls)
+ ## If the key was already present in the collisions-list, it's
+ ## value gets updated.
+ (#;Some coll-idx)
+ (#Collisions _hash (update! coll-idx [key val] _colls))
+
+ ## Otherwise, the KV-pair is added to the collisions-list.
+ #;None
+ (#Collisions _hash (insert! (array;size _colls) [key val] _colls)))
+ ## If the hashes are not equal, I create a new #Base node that
+ ## contains the old #Collisions node, plus the new KV-pair.
+ (|> (#Base (bit-position level _hash)
+ (|> (: (Base ($ 0) ($ 1))
+ (array;new +1))
+ (array;put +0 (#;Left node))))
+ (put' level hash key val Hash<K>)))
+ ))
+
+(def: (remove' level hash key Hash<K> node)
+ (All [K V] (-> Level Hash-Code K (Hash K) (Node K V) (Node K V)))
+ (case node
+ ## For #Hierarchy nodes, find out if there's a valid sub-node for
+ ## the Hash-Code.
+ (#Hierarchy h-size h-array)
+ (let [idx (level-index level hash)]
+ (case (array;get idx h-array)
+ ## If not, there's nothing to remove.
+ #;None
+ node
+
+ ## But if there is, try to remove the key from the sub-node.
+ (#;Some sub-node)
+ (let [sub-node' (remove' (level-up level) hash key Hash<K> sub-node)]
+ ## Then check if a removal was actually done.
+ (if (== sub-node sub-node')
+ ## If not, then there's nothing to change here either.
+ node
+ ## But if the sub-removal yielded an empty sub-node...
+ (if (empty?' sub-node')
+ ## Check if it's due time for a demotion.
+ (if (<=+ demotion-threshold h-size)
+ ## If so, perform it.
+ (#Base (demote-hierarchy idx [h-size h-array]))
+ ## Otherwise, just clear the space.
+ (#Hierarchy (dec+ h-size) (vacant! idx h-array)))
+ ## But if the sub-removal yielded a non-empty node, then
+ ## just update the hiearchy branch.
+ (#Hierarchy h-size (update! idx sub-node' h-array)))))))
+
+ ## For #Base nodes, check whether the BitPosition is set.
+ (#Base bitmap base)
+ (let [bit (bit-position level hash)]
+ (if (bit-position-is-set? bit bitmap)
+ (let [idx (base-index bit bitmap)]
+ (case (array;get idx base)
+ #;None
+ (undefined)
+
+ ## If set, check if it's a sub-node, and remove the KV
+ ## from it.
+ (#;Some (#;Left sub-node))
+ (let [sub-node' (remove' (level-up level) hash key Hash<K> sub-node)]
+ ## Verify that it was removed.
+ (if (== sub-node sub-node')
+ ## If not, there's also nothing to change here.
+ node
+ ## But if it came out empty...
+ (if (empty?' sub-node')
+ ### ... figure out whether that's the only position left.
+ (if (only-bit-position? bit bitmap)
+ ## If so, removing it leaves this node empty too.
+ empty
+ ## But if not, then just unset the position and
+ ## remove the node.
+ (#Base (unset-bit-position bit bitmap)
+ (remove! idx base)))
+ ## But, if it didn't come out empty, then the
+ ## position is kept, and the node gets updated.
+ (#Base bitmap
+ (update! idx (#;Left sub-node') base)))))
+
+ ## If, however, there was a KV pair instead of a sub-node.
+ (#;Some (#;Right [key' val']))
+ ## Check if the keys match.
+ (if (:: Hash<K> = key key')
+ ## If so, remove the KV pair and unset the BitPosition.
+ (#Base (unset-bit-position bit bitmap)
+ (remove! idx base))
+ ## Otherwise, there's nothing to remove.
+ node)))
+ ## If the BitPosition is not set, there's nothing to remove.
+ node))
+
+ ## For #Collisions nodes, It need to find out if the key already existst.
+ (#Collisions _hash _colls)
+ (case (collision-index Hash<K> key _colls)
+ ## If not, then there's nothing to remove.
+ #;None
+ node
+
+ ## But if so, then check the size of the collisions list.
+ (#;Some idx)
+ (if (=+ +1 (array;size _colls))
+ ## If there's only one left, then removing it leaves us with
+ ## an empty node.
+ empty
+ ## Otherwise, just shrink the array by removing the KV pair.
+ (#Collisions _hash (remove! idx _colls))))
+ ))
+
+(def: (get' level hash key Hash<K> node)
+ (All [K V] (-> Level Hash-Code K (Hash K) (Node K V) (Maybe V)))
+ (case node
+ ## For #Hierarchy nodes, just look-up the key on its children.
+ (#Hierarchy _size hierarchy)
+ (case (array;get (level-index level hash) hierarchy)
+ #;None #;None
+ (#;Some sub-node) (get' (level-up level) hash key Hash<K> sub-node))
+
+ ## For #Base nodes, check the leaves, and recursively check the branches.
+ (#Base bitmap base)
+ (let [bit (bit-position level hash)]
+ (if (bit-position-is-set? bit bitmap)
+ (case (array;get (base-index bit bitmap) base)
+ #;None
+ (undefined)
+
+ (#;Some (#;Left sub-node))
+ (get' (level-up level) hash key Hash<K> sub-node)
+
+ (#;Some (#;Right [key' val']))
+ (if (:: Hash<K> = key key')
+ (#;Some val')
+ #;None))
+ #;None))
+
+ ## For #Collisions nodes, do a linear scan of all the known KV-pairs.
+ (#Collisions _hash _colls)
+ (:: Monad<Maybe> map product;right
+ (array;find (|>. product;left (:: Hash<K> = key))
+ _colls))
+ ))
+
+(def: (size' node)
+ (All [K V] (-> (Node K V) Nat))
+ (case node
+ (#Hierarchy _size hierarchy)
+ (Array/fold ++ +0 (Array/map size' hierarchy))
+
+ (#Base _ base)
+ (Array/fold ++ +0 (Array/map (lambda [sub-node']
+ (case sub-node'
+ (#;Left sub-node) (size' sub-node)
+ (#;Right _) +1))
+ base))
+
+ (#Collisions hash colls)
+ (array;size colls)
+ ))
+
+(def: (entries' node)
+ (All [K V] (-> (Node K V) (List [K V])))
+ (case node
+ (#Hierarchy _size hierarchy)
+ (Array/fold (lambda [sub-node tail] (List/append (entries' sub-node) tail))
+ #;Nil
+ hierarchy)
+
+ (#Base bitmap base)
+ (Array/fold (lambda [branch tail]
+ (case branch
+ (#;Left sub-node)
+ (List/append (entries' sub-node) tail)
+
+ (#;Right [key' val'])
+ (#;Cons [key' val'] tail)))
+ #;Nil
+ base)
+
+ (#Collisions hash colls)
+ (Array/fold (lambda [[key' val'] tail] (#;Cons [key' val'] tail))
+ #;Nil
+ colls)))
+
+## [Exports]
+(type: #export (Dict k v)
+ {#;doc "A dictionary implemented as a Hash-Array Mapped Trie (HAMT)."}
+ {#hash (Hash k)
+ #root (Node k v)})
+
+(def: #export (new Hash<K>)
+ (All [K V] (-> (Hash K) (Dict K V)))
+ {#hash Hash<K>
+ #root empty})
+
+(def: #export (put key val [Hash<K> node])
+ (All [K V] (-> K V (Dict K V) (Dict K V)))
+ [Hash<K> (put' root-level (:: Hash<K> hash key) key val Hash<K> node)])
+
+(def: #export (remove key [Hash<K> node])
+ (All [K V] (-> K (Dict K V) (Dict K V)))
+ [Hash<K> (remove' root-level (:: Hash<K> hash key) key Hash<K> node)])
+
+(def: #export (get key [Hash<K> node])
+ (All [K V] (-> K (Dict K V) (Maybe V)))
+ (get' root-level (:: Hash<K> hash key) key Hash<K> node))
+
+(def: #export (contains? key table)
+ (All [K V] (-> K (Dict K V) Bool))
+ (case (get key table)
+ #;None false
+ (#;Some _) true))
+
+(def: #export (put~ key val table)
+ {#;doc "Only puts the KV-pair if the key is not already present."}
+ (All [K V] (-> K V (Dict K V) (Dict K V)))
+ (if (contains? key table)
+ table
+ (put key val table)))
+
+(def: #export (update key f table)
+ {#;doc "Transforms the value located at key (if available), using the given function."}
+ (All [K V] (-> K (-> V V) (Dict K V) (Dict K V)))
+ (case (get key table)
+ #;None
+ table
+
+ (#;Some val)
+ (put key (f val) table)))
+
+(def: #export size
+ (All [K V] (-> (Dict K V) Nat))
+ (|>. product;right size'))
+
+(def: #export empty?
+ (All [K V] (-> (Dict K V) Bool))
+ (|>. size (=+ +0)))
+
+(def: #export (entries dict)
+ (All [K V] (-> (Dict K V) (List [K V])))
+ (entries' (product;right dict)))
+
+(def: #export (from-list Hash<K> kvs)
+ (All [K V] (-> (Hash K) (List [K V]) (Dict K V)))
+ (List/fold (lambda [[k v] dict]
+ (put k v dict))
+ (new Hash<K>)
+ kvs))
+
+(do-template [<name> <elem-type> <side>]
+ [(def: #export <name>
+ (All [K V] (-> (Dict K V) (List <elem-type>)))
+ (|>. entries (List/map <side>)))]
+
+ [keys K product;left]
+ [values V product;right]
+ )
+
+(def: #export (merge dict2 dict1)
+ (All [K V] (-> (Dict K V) (Dict K V) (Dict K V)))
+ (List/fold (lambda [[key val] dict] (put key val dict))
+ dict1
+ (entries dict2)))
+
+(def: #export (merge-with f dict1 dict2)
+ (All [K V] (-> (-> V V V) (Dict K V) (Dict K V) (Dict K V)))
+ (List/fold (lambda [[key val] dict]
+ (case (get key dict)
+ #;None
+ (put key val dict)
+
+ (#;Some val')
+ (put key (f val' val) dict)))
+ dict1
+ (entries dict2)))
+
+(def: #export (re-bind from-key to-key dict)
+ (All [K V] (-> K K (Dict K V) (Dict K V)))
+ (case (get from-key dict)
+ #;None
+ dict
+
+ (#;Some val)
+ (|> dict
+ (remove from-key)
+ (put to-key val))))
+
+(def: #export (select keys (^@ old-dict [Hash<K> _]))
+ {#;doc "Creates a sub-set of the given dict, with only the specified keys."}
+ (All [K V] (-> (List K) (Dict K V) (Dict K V)))
+ (List/fold (lambda [key new-dict]
+ (case (get key old-dict)
+ #;None new-dict
+ (#;Some val) (put key val new-dict)))
+ (new Hash<K>)
+ keys))
+
+## [Structures]
+(struct: #export (Eq<Dict> Eq<v>) (All [k v] (-> (Eq v) (Eq (Dict k v))))
+ (def: (= test subject)
+ (and (=+ (size test)
+ (size subject))
+ (list;every? (lambda [k]
+ (case [(get k test) (get k subject)]
+ [(#;Some tk) (#;Some sk)]
+ (:: Eq<v> = tk sk)
+
+ _
+ false))
+ (keys test)))))