A-List utilities

Utilities to manipulate alists, i.e. association lists, i.e. a list of key-value pairs.

To use the bindings from this module:

(import :std/misc/alist)

alist?

(alist? alist) -> boolean

  alist := association list to check

Checks whether alist is a proper association list and returns a truth value (#t or #f). alist needs to be finite, circular lists are not supported.

A proper association list is a list of pairs and may be of the following forms:

  • ((key1 . value1) ...)
  • ((key1 value1) ...)

Examples:

> (alist? '((a . 1) (b . 2) (c . 3)))
#t

> (alist? [["one" #\1] ["two" #\2] ["three" #\3]])
#t

> (alist? '((a . 1) ("two" #\2) (1 2 3 4)))
#t    ; (1 2 3 4) is equivalent to (1 . (2 3 4))

> (alist? '(a 1 b 2 c 3))
#f    ; input is a plist, see plist? function

> (alist? '())
#t    ; edge-case, just like (list? '()) => #t

plist->alist

(plist->alist plist) -> alist | error

  plist := property list to transform

Transforms a property list (k1 v1 k2 v2 ...) into an association list ((k1 . v1) (k2 . v2)...). plist needs to be finite, circular lists are not supported. Furthermore, an error is signaled when plist is a improper property list.

Examples:

> (plist->alist [10 "cat" 11 "dog" 12 "bird"])
((10 . "cat") (11 . "dog") (12 . "bird"))

> (plist->alist ["semicolon" #\; "comma" #\, "dot"])
error    ; key "dot" has no associated property value

> (plist->alist [])
()

assq-set!, assv-set!, assoc-set!

(asetq! alist key value)
(assq-set! key alist value)

(asetv! alist key value)
(assv-set! key alist value)

(aset! alist key value)
(assoc-set! key alist value)

These functions kind-of complement the assq, assv, assoc functions from the prelude, and enable the destructive update of an alist (association list), i.e. a association list that follows the [[key1 . value1] [key2 . value2] ... [keyN . valueN]], by either modifying in-place an entry with the given key, or adding a new entry. The functions all return #!void.

Just like the alist getter functions, these functions are distinguished by which equality predicate is used to compare keywords: eq?, eqv? or equal? respectively. eq? is best for keys being symbols and keywords, eqv? for numbers, and equal? for strings or lists, etc.

Each function comes in two variant: the first one accepts (asetq! alist key value) as the order of arguments, whereas the second one follows the convention so that you can use (set! (assq key alist) value) or (set! (assv key alist) value). Note that in the last case, assq, assv and assoc return a pair, whereas the setter functions take just the value as argument. That's why we say these setter functions only "kind-of" complement the respective getter functions.

Last but not least, destructive operations are not allowed on an empty alist. If you use aset! or its friends, you have to ensure your alists are never empty. For instance you may keep a dummy key at the end of your alist that never gets removed. If this constraint is not acceptable, you may instead storing your alist in a variable (or struct field), use the pure aset operation, and update the variable (or struct field) with the result of it.

Examples:

(let (p [['a . 1]['b . 2]]) (asetq! p 'a 3) p)
> [['a . 3]['b . 2]]

(let (p [['a . 1]['b . 2]]) (asetq! p 'c 3) p)
> [['c . 3]['a . 1]['b . 4]]

aremq!, aremv!, arem!

(aremq! key plist)
(aremv! key plist)
(arem! key plist)

These functions destructively modify an alist (association-list) to remove the entry for a given key. Just like the alist getter and setter functions, these functions are distinguished by which equality predicate is used to compare keywords: eq?, eqv? or equal? respectively. See aset! above about alists. The functions all return #!void.

It is not allowed to destructively remove the last entry in an alist. If you use arem! or its friends, you have to ensure your alists are never empty. For instance you may keep a dummy key at the end of your alist that never gets removed. If this constraint is not acceptable, you may instead storing your alist in a variable (or struct field), use the pure arem operation, and update the variable (or struct field) with the result of it.

Examples:

(let (p [['a . 1]['b . 2]]) (aremq! 'a p) p)
> [['b . 2]]

(let (p [['a . 1]['b . 2]]) (arem! 'c p) p)
> [['a . 1]['b . 2]]

acons

(acons k v alist) -> alst

  k := key
  v := value
  alist := association list
  alst := new association list with additional binding

Adds a new key-value pair to an existing alist in a pure way, a bit like aset. Unlike aset and its friends, however, acons does not try to replace older bindings for the same key, and will instead shadow them. This can cause performance issues if a same key is used a lot of times with acons, with the list growing ever longer; this can cause even more interesting correctness issues if arem is used subsequently used, which will remove only the most recent binding, revealing the previous one again, which may or may not be the desired behavior. Thus, we recommend only using acons when you otherwise know k is not yet bound in the alist.

acons is analogous to the same-named function from Common Lisp, and (acons k v l) is synonymous with (cons (cons k v) l).

Examples:

> (acons 1 "a" '((2 . "b") (3 . "c")))
((1 . "a") (2 . "b") (3 . "c"))

> (acons 1 "a" '((1 . "b")))
((1 . "a") (1 . "b"))