1.1.1.2 Maps Tutorial

A map is an unordered collection of keys without duplicates, each key having an associated value. (Other languages call this concept a “hash” or “dict”.) As with sets, FSet imposes an ordering on the keys for its own purposes, but you shouldn’t expect this ordering to make sense.

You can create an empty map with empty-map; with on a map takes two arguments:

> (empty-map)
##{| |}
> (defparameter m1 (with (empty-map) 'x 1))
M1
> m1
##{| (X 1) |}

Retrieving the value associated with a map key is done with lookup or its synonym @ (which to use is a stylistic choice I’ll discuss further below; herein I’ll mostly use @):

> (lookup m1 'x)
1
T     ; second value indicates whether the key was found
X     ; third value is the key object in the map (ignore this for now)
> ( m1 'x)
1
T
X
> ( m1 'y)
NIL
NIL
NIL

As you see, these functions return a second value indicating whether the key was in the map. This is in case you’re using nil as a range element; since such usage is rare, most of the time you can ignore the second value. Also as you see, the “default default” for the primary value, when the key is not in the map, is nil. The default can be changed, though, either by supplying an argument to empty-map when first creating the map, or later by using with-default:

> (defparameter m2 (with (empty-map :default 0) 'x 1))
M2
> m2
##{| (X 1) |}/0
> ( m2 'x)
1
T
X
> ( m2 'y)
0
NIL
NIL
> (setq m2 (with-default m2 3))
##{| (X 1) |}/3
> ( m2 'y)
3
NIL
NIL

(When the default is not nil, it is printed after the map, separated by a slash.)

There is also a constructor macro for maps called map — the obvious choice of name except that it requires shadowing cl:map, which is a little unfortunate (but see the discussion of GMap below). The map macro has a more complex syntax than set since it accepts pairs:

> (defparameter m3 (map ('a 1) ('b 2)))
M3
> m3
##{| (B 2) (A 1) |}

Note that the sublists immediately within the map form are not forms, but pairs of forms (e.g. ('a 1) is not evaluated, but 'a and 1 are evaluated separately). The macro also supports the $ syntax for including the pairs of another map:

> (defparameter m4 (map ('b 3) ('c 4)))
M4
> m4
##{| (B 3) (C 4) |}
> (defparameter m5 (map ($ m3) ('D 42) ($ m4)))
M5
> m5
##{| (B 3) (C 4) (A 1) (D 42) |}

As you see, when two of the maps included with $ have pairs with the same key, the one on the right wins.

The ‘map‘ macro also gives you a convenient way to specify the default for a map:

> (defparameter m6 (map ('e 5) :default 0))
M6
> m6
##{| (E 5) |}/0

It is possible for a map to have no default. This case is distinct from a default of nil in two ways: the map prints with /[no default] on the end, and an error is signalled by lookup or @ when the key is not in the map:

> (defparameter m7 (empty-map :no-default? t))
M7
> m7
##{| |}/[no default]
> ( m7 'a)
error signalled (use the debugger's "abort" command to return to the REPL)

There are some interesting and powerful operations to combine maps. map-difference-2 returns, as two values, the mappings in its first argument that are not in its second, and conversely:

> (map-difference-2 m3 m5)
##{| (B 2) |}/[no default]
##{| (B 3) (C 4) (D 42) |}/[no default]

Here, the fact that the returned maps have no default tells you that the defaults of the arguments were equal.

The functions map-union and map-intersection return a map whose domain is respectively the union or intersection of the domains of the arguments. To compute the range value for each key that appears on both maps, they call a function supplied as their optional third argument; this function defaults to one that just returns its second argument, so the second map shadows the first one:

> (map-union m3 m4)
##{| (A 1) (B 3) (C 4) |}
> (defparameter m8 (map ('a (set 1)) ('b (set 2)) :default (set)))
M8
> m8
##{| (A ##{ 1 }) (B ##{ 2 }) |}/##{ }
> (defparameter m9 (map ('b (set "x")) ('c (set "y")) :default (set)))
M9
> m9
##{| (B ##{ "x "}) (C ##{" y" }) |}/##{ }
> (map-union m8 m9 #'union)
##{| (A ##{ 1 }) (B ##{ 2 "x "}) (C ##{ "y "}) |}/##{ }
> (map-intersection m8 m9 #'union)
##{| (B ##{ 2 "x" }) |}/##{ }