WhizzML Reference Manual

4.9 Maps

4.9.1 Construction

(make-map list-of-keys list-of-values) \(\rightarrow \) map

make-map is the basic map constructor in WhizzML. This procedure takes two lists of equal length, and keys and values match by position.

(let (v0 3 v1 2) (make-map ["k0" "k1"] [v0 v1])) ;; => {"k0" 3 "k1" 2}
(let (ks ["k1" "k0"]) (make-map ks [v0 v1])) ;; => {"k1" 3 "k0" 2}

The list of keys and values of a map can be recovered via the procedures:

(keys map) \(\rightarrow \) list
(values map) \(\rightarrow \) list

So we have the following identities, for any lists ks and vs:

(= (keys (make-map ks vs)) ks)   ;; => true
(= (values (make-map ks vs)) vs) ;; => true

It is also possible to construct a new map using a subset of the keys and values in another one, via select-keys:

(select-keys map list-of-strigs) \(\rightarrow \) map

Non-existing keys are ignored. Thus, for instance:

(select-keys {"a" 2 "b" 12 "c" [1 2]} ["a" "b"]) ;; => {"a" 2 "b" 12}
  (select-keys {"a" 2 "b" 12 "c" [1 2]} ["c" "x"]) ;; => {"c" [1 2]}
  (select-keys {"a" 2 "b" 12 "c" [1 2]} ["d" "x"]) ;; => {}
  (select-keys {"a" 2 "b" 12 "c" [1 2]} []) ;; => {}

4.9.2 Accessors

Access to values in a map is provided by the map itself used as a function (see section 2.5 ), get and get-in, and contains? provides a membership test:

(contains? map str-key) \(\rightarrow \) boolean
(get map str-key [obj]) \(\rightarrow \) object
(get-in map list-of-keys-or-ints [obj]) \(\rightarrow \) object

To access the value associated with a key str-key in a map, we use get, which takes an optional third argument with the value to return in case the key is not found in map. get-in performs a lookup following nested maps, given a list of keys and list positions (for cases where the corresponding value in the path is a list) and, optionally, a default value to return if the element is not found.

(get {"a" 42 "b" 3} "a") ;; => 42
(get {"a" 42} "c" 21) ;; => 21
(get-in {"a" {"b" 34}} ["a" "b"]) ;; => 34
(get-in {"a" [1 2 {"b" [3 4]}]} ["a" 2 "b" 0]) ;; => 3
(contains? {"a" 42} "a") ;; => true
(contains? {"c" {"a" 3}}) ;; => false

However, it is more common to simply apply the map value as a procedure to the key or list of keys we are looking up:

({"a" 42 "b" 3} "a") ;; => 42
({"a" 42} "c" 21) ;; => 21
({"a" {"b" 34}} ["a" "b"]) ;; => 34
({"a" [1 2 {"b" [3 4]}]} ["a" 2 "b" 0]) ;; => 3

Applicability of maps thus makes explicit use of the get and get-in accessors unnecessary in the vast majority of cases 1 .

In order to perform lookups using a path that starts with a list value instead of a map, get-in is actually overloaded and can take a list as its first argument, making the following lookups valid:

([{"b" 2 "a" 22}] [0 "a"]) ;; => 22
([1 [2 [3 [4]]]] [1 1 1 0]) ;; => 4

To avoid undefined values, asking for a key (or key list) that is not contained in a map without providing a default value raises an error with code -15 (key not found).

4.9.3 Element insertion

As all WhizzML values, maps are immutable, but you can create new ones by adding values to an existing one, using assoc, assoc-in and merge:

(assoc map str-key1 obj1 …) \(\rightarrow \) map
(assoc-in map list-of-keys obj) \(\rightarrow \) map
(merge map1 map2) \(\rightarrow \) map

To add key/value pairs to a given map you can use assoc or assoc-in for nested keys. Besides the map, assoc takes an arbitrary number of alternating keys and values, and will raise a bad arity error (code -40) if passed an even number of arguments. merge adds to map1 all keys in map2, overriding any one already in the former.

(assoc {} "key" 42) ;; => {"key" 42}
(assoc {"age" 23} "name" "Johnny") ;; => {"age" 23 "name" "Johnny"}
(assoc {} "key0" {} "key1" [] "key3" 42)
  ;; => {"key0" {} "key1" [] "key3" 42}
(assoc-in {"foo" 42 "submap" {"k" 3}} ["submap" "m"] 23)
  ;; => {"foo" 42 "submap" {"k" 3 "m" 23}}
(merge {"a" 2 "b" 3} {"a" 8 "c" 5}) ;; => {"a" 8 "b" 3 "c" 5}

4.9.4 Element removal

One can remove elements from an existing map with dissoc and dissoc-in, which, again, won’t mutate their map arguments, but, rather, return a new map:

(dissoc map str1 …) \(\rightarrow \) map
(dissoc-in map list-of-keys) \(\rightarrow \) map

(dissoc {"a" 1 "b" 2 "c" 3} "a") ;; => {"b" 2 "c" 3}
  (dissoc {"a" 1 "b" 2 "c" 3} "a" "c") ;; => {"b" 2}
  (dissoc-in {"a" {"b" 3 "c" {"d" 5}}} ["a" "c" "d"])
    ;; => {"a" {"b" 3 "c" {}}}
  1. One can concoct scenarios in which the accessor functions are needed because, for instance, they are used (or stored) indirectly as first class values, but those situations arise very rarely in practice.