Abstract

A map represents an associative array of key/value pairs. It provides a fast read access (using an hashtable internally) and a convenient, dedicated syntax. Unlike the hash! datatype, a map is not a series, so does not have a concept of offset or positions. Conceptually, the map! datatype lies between the hash! and object! datatypes.

Literal syntax

   #(<key> <value>...)

   <key>  : hashed key, accepted types are:
                 any-word!, any-string!, integer!, float!, char!

   <value> : any-type! value

Creation syntax

   make map! <spec>

   <spec> : block of key/value pairs or an integer value

If the spec argument is an integer, an empty map! is created with a pre-allocated number of slots (usually in order to populate the map dynamically).

Notes: the map body or the spec block have to contain an even number of elements, or else, an error will be generated. values are not reduced, so construction syntax is required for some special values, like logic!.

Examples:

#(a: 1 b: 2)
== #(
    a: 1
    b: 2
)

make map! [a 1 'b 2 "c" 3]
== #(
    a: 1
    b: 2
    "c" 3
)

If the key is of any-word! type, the key type is converted to set-word! in the map, in order to make the map content look more like pairs of keys with value assigned. Though, in order to access word keys, there is no requirement to provide a set-word, for practical reasons (easier construction, especially in paths), simple words can be used. Similarly, keys-of reflector (described in Reflection section below), will return words instead of set-words, as it simplifies further processing (especially matching operations are easier with words rather than set-words).

Notes: like hash! and block!, map! is case-preserving, and case-insensitive for lookup by default. if a none value is specified as value, the key will not be created (see "Deleting keys" section). * Any-string! keys and series! values are not copied on creation of the map, the choice is left to the user (this optimizes resources for the common use-case).

Another way to create a new map is to use the copy action on an existing one.

Retrieving values

Using paths:

    <map>/<key>
    get '<map>/<key>

    <map> : word referring to a map! value
    <key> : word key

Using selecting action:

    select <map> <key>

    <map> : map value
    <key> : any valid key type

All these read accesses are case-insensitive. In order to have a case-sensitive lookup, the /case refinement needs to be used where available:

    get/case '<map>/<key>
    select/case <map> <key>

Trying to access a key not defined in a map will return a none value.

Examples:

   m: #(Ab: 2 aB: 5 ab: 10)
   m/ab
   == 2
   select m 'aB
   == 2
   get/case 'm/aB
   == 5
   select/case m 'ab
   == 10

Changing keys and values

Using paths:

    <map>/<key>: <value>
    set '<map>/<key> <value>

    <map>   : word referring to a map! value
    <key>   : word key to select a value in the map
    <value> : any value

Using modifying action:

    put <map> <key> <value>

    <map> : map value
    <key> : any valid key value to a select a value in the map

Making bulk changes:

    extend <map> <spec>

    <map>  : a map value
    <spec> : block of name/value pairs (one or more pairs)

All these write accesses are case-insensitive. In order to have a case-sensitive lookup, the /case refinement needs to be used where available:

    set/case '<map>/<key> <value>
    put/case <map> <key> <value>
    extend/case <map> <spec>

extend native can accept many keys at the same time, so it is convenient for bulk changes.

Notes: setting a key that does not exist previously in the map, will simply create it. adding an existing key will change the key value and not add a new one (case-insensitive matching by default).

Examples:

   m: #(Ab: 2 aB: 5 ab: 10)
   m/ab: 3
   m
   == #(
      Ab: 3
      aB: 5
      ab: 10
   )

   put m 'aB "hello"
   m
   == #(
      Ab: "hello"
      aB: 5
      ab: 10
   )

   set/case 'm/aB 0
   m
   == #(
      Ab: "hello"
      aB: 0
      ab: 10
   )
   set/case 'm/ab 192.168.0.1
   == #(
      Ab: "hello"
      aB: 0
      ab: 192.168.0.1
   )

   m: #(%cities.red 10)
   extend m [%cities.red 99 %countries.red 7 %states.red 27]
   m
   == #(
       %cities.red 99
       %countries.red 7
       %states.red 27
    )

Deleting keys

In order to delete a key/value pair from a map, you simply set the key to none value using one of the available ways.

Example:

    m: #(a: 1 b 2 "c" 3 d: 99)
    m
    == #(
        a: 1
        b: 2
        "c" 3
        d: 99
    )
    m/b: none
    put m "c" none
    extend m [d #[none]]
    m
    == #(
        a: 1
    )

Note: construction syntax is required in the above example in order to pass a none! value and not a word! value (just one way to construct the spec block needed there).

It is also possible to delete all keys at same time using clear action:

clear #(a 1 b 2 c 3)
== #()

Reflection

  • find checks if a key is defined in a map and returns true if found, or else none.
find #(a 123 b 456) 'b
== true
  • length? returns the number of a key/value pairs in a map.
length? #(a 123 b 456)
== 2
  • keys-of returns the list of keys from a map in a block (set-words are converted to words).
keys-of #(a: 123 b: 456)
== [a b]
  • values-of returns the list of values from a map in a block.
values-of #(a: 123 b: 456)
== [123 456]
  • body-of returns all the key/value pairs from a map in a block.
body-of #(a: 123 b: 456)
== [a: 123 b: 456]