map.el vs. other libraries through hundreds of examples

If you code in Emacs Lisp, you'll deal with alists, plists, hash tables, and other kinds of key–value collections — also known as maps.

Today we're going to compare map-manipulation functions. Our focus will be on a few notable places where you can find them:

The reasons why you'd prefer one library over another is a broad topic. Our scope here is narrower: to see them in action.

map.el

This article is centered on map.el. This is for three reasons:

  • It's a native library that has grown in user adoption.
  • It has over the years added more functions, which can deal with alists, plists, hash tables, and arrays.
  • Last, but not least, because I never seem to use it.

So what do I use instead?

More broadly, what else would produce the same results as map?

To answer these questions, I:

  • listed map.el's functions,
  • collected usage examples of each, sometimes adding examples of my own,
  • and, for each map example, answered: what alternatives do I see?

That's what you'll find below. We'll look at solutions side by side to get a sense of how they compare.

map.el's functions and their alternatives

A summary of where the functions come from:

;;; Libraries used here

;;;; map        (native)
(map-foo x y)     ; map

;;;; other  (non-native)
(-foo x y)        ; dash
(--foo x y)       ; dash
(->> x y z)       ; dash
(-> x y z)        ; dash
(h-foo x y)       ; xht
(h<-foo x)        ; xht
(h= x y z)        ; xht
(ht-foo x y)      ; ht
(ht<-foo x)       ; ht
(##foo % %2)      ; llama

;;;; all else   (native)
(let-alist a x)   ; let-alist
(foo else)        ; C source code, subr, or subr-x

For each map function:

  • I pick one or more examples, always showing a map example first.
  • This is followed by one or more alternative solutions.
  • These alternatives will almost always be sorted from largest to shortest.

Let's see them.

Map Predicates

mapp

Although xht doesn't have this exact predicate (yet?), it can be composed with just two functions.

;;; some non-map thing
;;;; symbol
(mapp 'something)              => nil
(keywordp (h-type 'something)) => nil

;;;; number
(mapp 42)              => nil
(keywordp (h-type 42)) => nil
;;; hash table
;;;; general
(mapp #s(hash-table data (a 1 b 2))) => t
(keywordp (h-type (h* 'a 1 'b 2)))   => t

;;;; specific for hash tables
(hash-table-p #s(hash-table data (a 1 b 2))) => t  ; map
(ht-p (ht ('a 1) ('b 2)))                    => t  ; ht
(h? (h* 'a 1 'b 2))                          => t  ; xht
;;; vector
;;;; general
(mapp [a b c])              => t
(keywordp (h-type [a b c])) => t

;;;; specific for vectors
(vectorp [a b c]) => t
;;; alist
;;;; general
(mapp '((a . 1) (b . 2)))              => t
(keywordp (h-type '((a . 1) (b . 2)))) => t

;;;; specific for alists
(h-alist? '((a . 1) (b . 2))) => t
;;; plist
;;;; general
(mapp '(a 1 b 2))              => t
(keywordp (h-type '(a 1 b 2))) => t

;;;; specific for plists
(h-plist? '(a 1 b 2)) => t
(plistp '(a 1 b 2))   => t
Is '(a b c) a map? If so, of what type?

Good question. It depends on whom you ask.

;;;; map.el says yes, it's a map
(mapp '(a b c)) => t
;;;; map.el parses it as a plist whose last value is implicitly nil
(map-pairs '(a b c)) => '((a . b) (c))
;;;; subr.el, json.el, and xht.el disagree
(plistp       '(a b c)) => nil
(json-plist-p '(a b c)) => nil
(h-plist?     '(a b c)) => nil

;;;; For them, plists must have an even number of items
(defun plistp (object)
  "Non-nil if and only if OBJECT is a valid plist."
  (let ((len (proper-list-p object)))
    (and len (zerop (% len 2)))))
;;            ^^^^^^^^^^^^^^
;;;; json.el is even stricter: besides accepting no implicit trailing nil value...
(json-plist-p '(:a 1 :b))   => nil

;;;; ...keys must also be keywords
(json-plist-p '(a  1  b 2)) => nil
(json-plist-p '(:a 1 :b 2)) => t
;;;; To further complicate it, other native functions do treat it as plist
(plist-member '(a b c) 'd) => nil      ; ok, 'd is not there, not a key
(plist-member '(a b c) 'b) => nil      ; ok, 'b is a value, not a key
(plist-member '(a b c) 'a) => '(a b c) ; ok, 'a is a key
(plist-member '(a b c) 'c) => '(c)     ; well, 'c is a key!
;;;; Both xht and ht accept an explicit conversion by discarding the odd one
(h<-plist  '(a b c)) H=> (h* 'a 'b)
(ht<-plist '(a b c)) H=> (h* 'a 'b)
;;;; Nevertheless, xht actually identifies it as a simple list...
(h-type '(a b c))     => :list

;;;; ...and in a DWIM conversion takes its indices as keys...
(h<-it '(a b c))     H=> (h* 0 'a 1 'b 2 'c)

;;;; ...which is exactly what it would do with the corresponding vector
(h<-it [a b c])      H=> (h* 0 'a 1 'b 2 'c)
;;;; On the other hand, the list below would be considered a plist...
(plistp   '(a b c d)) => t
(h-plist? '(a b c d)) => t
(h-type   '(a b c d)) => :plist

;;;; ...and converted accordingly
(h<-it '(a b c d))   H=> (h* 'a 'b 'c 'd)
;;;; So we have this situation
(h<-it     [a b c d])             H=> (h* 0 'a 1 'b 2 'c 3 'd)
(map-into  [a b c d] 'hash-table) H=> (h* 0 'a 1 'b 2 'c 3 'd)

(h<-it    '(a b c d))             H=> (h* 'a 'b 'c 'd)
(map-into '(a b c d) 'hash-table) H=> (h* 'a 'b 'c 'd)

(h<-it     [a b c])               H=> (h* 0 'a 1 'b 2 'c)
(map-into  [a b c]   'hash-table) H=> (h* 0 'a 1 'b 2 'c)

(h<-it    '(a b c))               H=> (h* 0 'a 1 'b 2 'c)  ; analogy with [a b c]
(map-into '(a b c)   'hash-table) H=> (h* 'a 'b 'c nil)    ; analogy with '(a b c d)

For map.el, it seems that we have:

Is this-list an alist? No? Then it's a plist.

For xht.el, it looks more like:

Is type-of this-list the symbol 'cons? Yes?
Then is it a cons pair? No?
Is it a list of lists? No?
An alist, then? No?
Perhaps a plist — does it have an even number of items? Also no?
Well, then we'll treat it as a generic simple list.

Here's the relevant part from h-type that does that:

('cons (cond
        ((-cons-pair-p obj) :cons-pair)
        ((h-lol?       obj) :lol)
        ((h-alist?     obj) :alist)
        ((h-plist?     obj) :plist)
        (t                  :list)))

Different designs.

So what to do in xht when you find yourself converting an ambiguous input type?

;;;; Do What I Mean
(h<-it    '(a b c d))   H=> (h* 'a 'b 'c 'd)     ; "even number, most likely a plist..."
(h<-it    '(a b c))     H=> (h* 0 'a 1 'b 2 'c)  ; "odd number, probably not a plist..."

Answer: consider using an exact conversion function instead.

;;;; Do What I Say
(h<-plist '(a b c d))   H=> (h* 'a 'b 'c 'd)
(h<-plist '(a b c))     H=> (h* 'a 'b)

(h<-list  '(a b c d))   H=> (h* 0 'a 1 'b 2 'c 3 'd)
(h<-list  '(a b c))     H=> (h* 0 'a 1 'b 2 'c)
Another mismatch
;;;; simple string

;;;;; map.el autodetects a vector
(mapp "just a\nstring") => t
(map-keys "just a\nstring") => '(0 1 2 3 4 5 6 7 8 9 10 11 12)
(map-values "just a\nstring")
=> '(106 117 115 116 32 97 10 115 116 114 105 110 103)
(concat (map-values "just a\nstring")) => "just a\nstring"

;;;;; whereas xht.el autodetects lines of string...
(keywordp (h-type "just a\nstring")) => t
(h-type "just a\nstring") => :lines
(-> "just a\nstring"  h<-it  h-keys) => '(0 1)
(-> "just a\nstring"  h<-it  h-values) => '("just a" "string")

;;;;; ...but could also treat it as vector
(-> "just a\nstring"  h<-vector  h-keys) => '(0 1 2 3 4 5 6 7 8 9 10 11 12)
(-> "just a\nstring"  h<-vector  h-values)
=> '(106 117 115 116 32 97 10 115 116 114 105 110 103)
(-> "just a\nstring"  h<-vector  h-values  concat) => "just a\nstring"

map-contains-key

;;; hash table
(map-contains-key #s(hash-table data (x 1 y 2)) 'y) => t  ; map
(and (gethash 'y #s(hash-table data (x 1 y 2))) t)  => t  ; C src
(ht-contains? (ht ('x 1) ('y 2)) 'y)                => t  ; ht
(h-has-key? (h* 'x 1 'y 2) 'y)                      => t  ; xht
;;; alist
(map-contains-key '((x . 1) (y . 2)) 'y)         => t  ; map
(ht-contains? (ht<-alist '((x . 1) (y . 2))) 'y) => t  ; ht
(h-has-key? (h<-alist '((x . 1) (y . 2))) 'y)    => t  ; xht
(and (alist-get 'y '((x . 1) (y . 2))) t)        => t  ; C src
;;; plist
(and (map-contains-key '(:x  1 :y  2) :y) t) => t  ; map
(ht-contains? (ht<-plist '(:x  1 :y  2)) :y) => t  ; ht
(h-has-key? (h<-plist '(:x  1 :y  2)) :y)    => t  ; xht
(and (plist-get '(:x  1 :y  2) :y) t)        => t  ; C src
;;; vector
(map-contains-key [x y] 1)       => t  ; map
(h-has-key? (h<-vector [x y]) 1) => t  ; xht
(> (length [x y]) 1)             => t  ; C src

map-empty-p

;;; hash table
(map-empty-p #s(hash-table data ()))            => t  ; map
(= 0 (hash-table-count #s(hash-table data ()))) => t  ; C src
(hash-table-empty-p #s(hash-table data ()))     => t  ; subr-x
(= 0 (h-length (h*)))                           => t  ; xht
(= 0 (ht-size (ht)))                            => t  ; ht
(ht-empty? (ht))                                => t  ; ht
(h-empty? (h*))                                 => t  ; xht
;;; alist
(map-empty-p ())               => t  ; map
(= 0 (ht-size (ht<-alist ()))) => t  ; ht
(= 0 (h-length (h<-it ())))    => t  ; xht
(ht-empty? (ht<-alist ()))     => t  ; ht
(h-empty? (h<-it ()))          => t  ; xht
(= 0 (length ()))              => t  ; C src
(h-it-empty? ())               => t  ; xht
;;; vector
(map-empty-p [])            => t  ; map
(= 0 (h-length (h<-it []))) => t  ; xht
(h-empty? (h<-it []))       => t  ; xht
(= 0 (length []))           => t  ; C src
(h-it-empty? [])            => t  ; xht

map-every-p

Note that both xht and ht already require dash.

So I won't add + dash to any solutions that use either of these two libraries, even if some additional dash function is used.

;;; alist
(map-every-p (lambda (k v) (= (* k k) v)) '((3 . 9) (4 . 16)))                => t  ; map
(not (h-any (-not (lambda (k v) (= (* k k) v))) (h<-it '((3 . 9) (4 . 16))))) => t  ; xht
(h-empty? (h-rej (lambda (k v) (= (* k k) v)) (h<-it '((3 . 9) (4 . 16)))))   => t  ; xht
(not (h--any (not (= (* key key) value)) (h<-it '((3 . 9) (4 . 16)))))        => t  ; xht
(h-empty? (h--rej (= (* key key) value) (h<-it '((3 . 9) (4 . 16)))))         => t  ; xht
(not (h-any (-not (##= (* % %) %2)) (h<-it '((3 . 9) (4 . 16)))))             => t  ; xht + llama
(h-all? (lambda (k v) (= (* k k) v)) (h<-it '((3 . 9) (4 . 16))))             => t  ; xht
(h-all (lambda (k v) (= (* k k) v)) (h<-it '((3 . 9) (4 . 16))))              => t  ; xht
(h-empty? (h-rej (##= (* % %) %2) (h<-it '((3 . 9) (4 . 16)))))               => t  ; xht + llama
(-every (-lambda ((k . v)) (= (* k k) v)) '((3 . 9) (4 . 16)))                => t  ; dash
(-all? (-lambda ((k . v)) (= (* k k) v)) '((3 . 9) (4 . 16)))                 => t  ; dash
(--every (= (expt (car it) 2) (cdr it)) '((3 . 9) (4 . 16)))                  => t  ; dash
(--all? (= (expt (car it) 2) (cdr it)) '((3 . 9) (4 . 16)))                   => t  ; dash
(h--all? (= (* key key) value) (h<-it '((3 . 9) (4 . 16))))                   => t  ; xht
(h--all (= (* key key) value) (h<-it '((3 . 9) (4 . 16))))                    => t  ; xht
(h-all? (##= (* % %) %2) (h<-it '((3 . 9) (4 . 16))))                         => t  ; xht + llama
(h-all (##= (* % %) %2) (h<-it '((3 . 9) (4 . 16))))                          => t  ; xht + llama
(map-every-p (##= (* % %) %2) '((3 . 9) (4 . 16)))                            => t  ; map + llama

(Note that all and all? (in xht), and every and all? (in dash), be it regular or anaphoric, only return the same result here because the predicate used in the example above is #'=, which returns nil or t.)

Now, if we were dealing with hash tables:

;;; hash table
(map-every-p (lambda (k v) (= (* k k) v)) #s(hash-table data (3 9 4 16))) => t  ; map
(not (h-any (-not (lambda (k v) (= (* k k) v))) (h* 3 9 4 16)))           => t  ; xht
(map-every-p (##= (* % %) %2) #s(hash-table data (3 9 4 16)))             => t  ; map + llama
(h-empty? (h-rej (lambda (k v) (= (* k k) v)) (h* 3 9 4 16)))             => t  ; xht
(not (h--any (not (= (* key key) value)) (h* 3 9 4 16)))                  => t  ; xht
(h-empty? (h--rej (= (* key key) value) (h* 3 9 4 16)))                   => t  ; xht
(not (h-any (-not (##= (* % %) %2)) (h* 3 9 4 16)))                       => t  ; xht + llama
(h-all? (lambda (k v) (= (* k k) v)) (h* 3 9 4 16))                       => t  ; xht
(h-all (lambda (k v) (= (* k k) v)) (h* 3 9 4 16))                        => t  ; xht
(h-empty? (h-rej (##= (* % %) %2) (h* 3 9 4 16)))                         => t  ; xht + llama
(h--all? (= (* key key) value) (h* 3 9 4 16))                             => t  ; xht
(h--all (= (* key key) value) (h* 3 9 4 16))                              => t  ; xht
(h-all? (##= (* % %) %2) (h* 3 9 4 16))                                   => t  ; xht + llama
(h-all (##= (* % %) %2) (h* 3 9 4 16))                                    => t  ; xht + llama

Note that although the combination of llama + h-all would offer the shortest overall solution here, it wouldn't be quite as readable as the two pure xht anaphorics above it, in both of whose forms the arguments being passed — key and value — are more immediately obvious.

Likewise for llama + h-any in the map-some examples below.

map-some

With "regular" boolean predicates:

;;; alist
(map-some (lambda (k v) (= (* k k) v)) '((3 . 9) (4 . 12)))                       => t  ; map
(not (h-empty? (h-sel (lambda (k v) (= (* k k) v)) (h<-it '((3 . 9) (4 . 12)))))) => t  ; xht
(not (h-empty? (h--sel (= (* key key) value) (h<-it '((3 . 9) (4 . 12))))))       => t  ; xht
(not (h-empty? (h-sel (##= (* % %) %2) (h<-it '((3 . 9) (4 . 12))))))             => t  ; xht + llama
(h-any? (lambda (k v) (= (* k k) v)) (h<-it '((3 . 9) (4 . 12))))                 => t  ; xht
(h-any (lambda (k v) (= (* k k) v)) (h<-it '((3 . 9) (4 . 12))))                  => t  ; xht
(-any? (-lambda ((k . v)) (= (* k k) v)) '((3 . 9) (4 . 12)))                     => t  ; dash
(-any (-lambda ((k . v)) (= (* k k) v)) '((3 . 9) (4 . 12)))                      => t  ; dash
(--any? (= (expt (car it) 2) (cdr it)) '((3 . 9) (4 . 12)))                       => t  ; dash
(h--any? (= (* key key) value) (h<-it '((3 . 9) (4 . 12))))                       => t  ; xht
(--any (= (expt (car it) 2) (cdr it)) '((3 . 9) (4 . 12)))                        => t  ; dash
(h--any (= (* key key) value) (h<-it '((3 . 9) (4 . 12))))                        => t  ; xht
(h-any? (##= (* % %) %2) (h<-it '((3 . 9) (4 . 12))))                             => t  ; xht + llama
(h-any (##= (* % %) %2) (h<-it '((3 . 9) (4 . 12))))                              => t  ; xht + llama
(map-some (##= (* % %) %2) '((3 . 9) (4 . 12)))                                   => t  ; map + llama

(Note that any and any? (in both xht and dash), be it regular or anaphoric, only return the same result because the predicate used in the examples above is #'=, which returns nil or t.)

;;; hash table
(map-some (lambda (k v) (= (* k k) v)) #s(hash-table data (3 9 4 12))) => t  ; map
(not (h-empty? (h-sel (lambda (k v) (= (* k k) v)) (h* 3 9 4 12))))    => t  ; xht
(not (h-empty? (h--sel (= (* key key) value) (h* 3 9 4 12))))          => t  ; xht
(map-some (##= (* % %) %2) #s(hash-table data (3 9 4 12)))             => t  ; map + llama
(not (h-empty? (h-sel (##= (* % %) %2) (h* 3 9 4 12))))                => t  ; xht + llama
(h-any? (lambda (k v) (= (* k k) v)) (h* 3 9 4 12))                    => t  ; xht
(h-any (lambda (k v) (= (* k k) v)) (h* 3 9 4 12))                     => t  ; xht
(h--any? (= (* key key) value) (h* 3 9 4 12))                          => t  ; xht
(h--any (= (* key key) value) (h* 3 9 4 12))                           => t  ; xht
(h-any? (##= (* % %) %2) (h* 3 9 4 12))                                => t  ; xht + llama
(h-any (##= (* % %) %2) (h* 3 9 4 12))                                 => t  ; xht + llama

With a predicate whose non-nil result is not t:

;;; hash table
(map-some #'string-match-p #s(hash-table data ("x" "quux" "a" "bar"))) => 3  ; map
(h-any #'string-match-p (h* "x" "quux" "a" "bar"))                     => 3  ; xht
;;; hash table
(map-some (lambda (k v) (string-match-p v k)) #s(hash-table data ("quux" "x" "bar" "a")))  ; map
(map-some (##string-match-p %2 %1) #s(hash-table data ("quux" "x" "bar" "a")))             ; map + llama
(map-some (-flip #'string-match-p) #s(hash-table data ("quux" "x" "bar" "a")))             ; map + dash
(h-any (lambda (k v) (string-match-p v k)) (h* "quux" "x" "bar" "a"))                      ; xht
(h--any (string-match-p value key) (h* "quux" "x" "bar" "a"))                              ; xht
(h-any (##string-match-p %2 %1) (h* "quux" "x" "bar" "a"))                                 ; xht + llama
(h-any (-flip #'string-match-p) (h* "quux" "x" "bar" "a"))                                 ; xht

=> 3

Building Maps

map-copy

;;; hash table
(h= (map-copy #s(hash-table data (a 1 b 2)))         ; map
    (copy-hash-table #s(hash-table data (a 1 b 2)))  ; C src
    (ht-copy (ht ('a 1) ('b 2)))                     ; ht
    (h-copy (h* 'a 1 'b 2)))                         ; xht
=> t
;;; alist
(map-copy '((a . 1) (b . 2)))   => '((a . 1) (b . 2))  ; map
(copy-alist '((a . 1) (b . 2))) => '((a . 1) (b . 2))  ; C src
;;; plist
(map-copy '(a 1 b 2))      => '(a 1 b 2)  ; map
(copy-sequence '(a 1 b 2)) => '(a 1 b 2)  ; C src
(-copy '(a 1 b 2))         => '(a 1 b 2)  ; dash
;;; vector
(map-copy [a b c])      => [a b c]  ; map
(copy-sequence [a b c]) => [a b c]  ; C src
(-copy [a b c])         => [a b c]  ; dash

map-into

(map-into #s(hash-table data (x 1 y 2)) 'list) => '((x . 1) (y . 2))  ; map
(nreverse (ht->alist (ht ('x 1) ('y 2))))      => '((x . 1) (y . 2))  ; ht
(h->alist (h* 'x 1 'y 2))                      => '((x . 1) (y . 2))  ; xht

map-pairs

;;; hash table
(map-pairs #s(hash-table data (x 1 y 2))) => '((x . 1) (y . 2))  ; map
(nreverse (ht->alist (ht ('x 1) ('y 2)))) => '((x . 1) (y . 2))  ; ht
(h->alist (h* 'x 1 'y 2))                 => '((x . 1) (y . 2))  ; xht
;;; plist
(map-pairs '(x 1 y 2))                       => '((x . 1) (y . 2))  ; map
(nreverse (ht->alist (h<-plist '(x 1 y 2)))) => '((x . 1) (y . 2))  ; ht
(h->alist (h<-it '(x 1 y 2)))                => '((x . 1) (y . 2))  ; xht
;;; vector
(map-pairs [a b c])        => '((0 . a) (1 . b) (2 . c))  ; map
(h->alist (h<-it [a b c])) => '((0 . a) (1 . b) (2 . c))  ; xht

Utility Functions

map-length

(Note: map.el has an open question under map-length's definition:

(cl-defgeneric map-length (map)
  ;; FIXME: Should we rename this to `map-size'?)

I'd argue that no, map.el should keep it as map-length.

Why? For the same reasons that led xht to choose h-length.)

;;; hash table
(map-length #s(hash-table data (a 1 b 2 c 3)))       => 3  ; map
(hash-table-count #s(hash-table data (a 1 b 2 c 3))) => 3  ; C src
(ht-size (ht ('a 1) ('b 2) ('c 3)))                  => 3  ; ht
(h-length (h* 'a 1 'b 2 'c 3))                       => 3  ; xht
;;; alist
(map-length '((a . 1) (b . 2) (c . 3)))          => 3  ; map
(ht-size (ht<-alist '((a . 1) (b . 2) (c . 3)))) => 3  ; ht
(h-length (h<-it '((a . 1) (b . 2) (c . 3))))    => 3  ; xht
(length '((a . 1) (b . 2) (c . 3)))              => 3  ; C src
;;; plist
(map-length '(:a 1 :b 2 :c 3))          => 3  ; map
(ht-size (ht<-plist '(:a 1 :b 2 :c 3))) => 3  ; ht
(h-length (h<-it '(:a 1 :b 2 :c 3)))    => 3  ; xht
(/ (length '(:a 1 :b 2 :c 3)) 2)        => 3  ; C src
;;; vector
(map-length [a b c])       => 3  ; map
(h-length (h<-it [a b c])) => 3  ; xht
(length [a b c])           => 3  ; C src

map-elt

;;; hash table
(map-elt #s(hash-table data (x 1 y 2)) 'y)          => 2  ; map
(-let [(&hash 'y) #s(hash-table data (x 1 y 2))] y) => 2  ; dash
(gethash 'y #s(hash-table data (x 1 y 2)))          => 2  ; C src
(-let [(&hash 'y) (h* 'x 1 'y 2)] y)                => 2  ; dash + xht
(ht-get (ht ('x 1) ('y 2)) 'y)                      => 2  ; ht
(h-get (h* 'x 1 'y 2) 'y)                           => 2  ; xht
(h-let (h* 'x 1 'y 2) .y)                           => 2  ; xht
;;; alist
(map-elt '((x . 1) (y . 2)) 'y)            => 2  ; map
(ht-get (ht<-alist '((x . 1) (y . 2))) 'y) => 2  ; ht
(-let [(&alist 'y) '((x . 1) (y . 2))] y)  => 2  ; dash
(h-get (h<-it '((x . 1) (y . 2))) 'y)      => 2  ; xht
(h-let (h<-it '((x . 1) (y . 2))) .y)      => 2  ; xht
(alist-get 'y '((x . 1) (y . 2)))          => 2  ; subr
(let-alist '((x . 1) (y . 2)) .y)          => 2  ; let-alist
(h-let-it '((x . 1) (y . 2)) .y)           => 2  ; xht
;;; plist
(map-elt '(x 1 y 2) 'y)            => 2  ; map
(ht-get (ht<-plist '(x 1 y 2)) 'y) => 2  ; ht
(-let [(&plist 'y) '(x 1 y 2)] y)  => 2  ; dash
(h-get (h<-it '(x 1 y 2)) 'y)      => 2  ; xht
(h-let (h<-it '(x 1 y 2)) .y)      => 2  ; xht
(plist-get '(x 1 y 2) 'y)          => 2  ; C src
(h-let-it '(x 1 y 2) .y)           => 2  ; xht
;;; vector
(map-elt [a b c] 1)       => 'b  ; map
(h-get (h<-it [a b c]) 1) => 'b  ; xht
(h-let-it [a b c] \.1)    => 'b  ; xht
(aref [a b c] 1)          => 'b  ; C src
(elt [a b c] 1)           => 'b  ; C src

map-nested-elt

;;; nested hash table
(map-nested-elt #s(hash-table data (post #s(hash-table data (title "foo")))) '(post title)) => "foo"  ; map
(ht-get* (ht ('post (ht ('title "foo")))) 'post 'title)                                     => "foo"  ; ht
(h-get* (h* 'post (h* 'title "foo")) 'post 'title)                                          => "foo"  ; xht
(h-let (h* 'post (h* 'title "foo")) .post.title)                                            => "foo"  ; xht
;;; nested alist
(map-nested-elt '((post . ((title . "foo")))) '(post title)) => "foo"  ; map
(h-get* (h<-it '((post . ((title . "foo"))))) 'post 'title)  => "foo"  ; xht
(let-alist '((post . ((title . "foo")))) .post.title)        => "foo"  ; let-alist
(h-let-it '((post . ((title . "foo")))) .post.title)         => "foo"  ; xht
;;; nested plist
(map-nested-elt '(post (title "foo")) '(post title)) => "foo"  ; map
(h-get* (h<-it '(post (title "foo"))) 'post 'title)  => "foo"  ; xht
(h-let-it '(post (title "foo")) .post.title)         => "foo"  ; xht

Mapping Over Maps

map-apply

;;; hash table
(map-apply (lambda (k v) (cons k v)) #s(hash-table data (x 1 y 2)))  ; map
(map-apply (-cut cons <> <>) #s(hash-table data (x 1 y 2)))          ; map + dash
(map-apply (##cons % %2) #s(hash-table data (x 1 y 2)))              ; map + llama
(h-lmap (lambda (k v) (cons k v)) (h* 'x 1 'y 2))                    ; xht
(nreverse (ht->alist (ht ('x 1) ('y 2))))                            ; ht
(h-lmap (-cut cons <> <>) (h* 'x 1 'y 2))                            ; xht
(h--lmap (cons key value) (h* 'x 1 'y 2))                            ; xht
(h-lmap (##cons % %2) (h* 'x 1 'y 2))                                ; xht + llama
(h->alist (h* 'x 1 'y 2))                                            ; xht

=> '((x . 1) (y . 2))
;;; hash table
(map-apply (lambda (k v) (format "%s = %s" k v)) #s(hash-table data (x 1 y 2)))  ; map
(map-apply (-cut format "%s = %s" <> <>) #s(hash-table data (x 1 y 2)))          ; map + dash
(nreverse (ht-amap (format "%s = %s" key value) (ht ('x 1) ('y 2))))             ; ht
(map-apply (##format "%s = %s" % %2) #s(hash-table data (x 1 y 2)))              ; map + llama
(h-lmap (-cut format "%s = %s" <> <>) (h* 'x 1 'y 2))                            ; xht
(h--lmap (format "%s = %s" key value) (h* 'x 1 'y 2))                            ; xht
(h-lmap (##format "%s = %s" % %2) (h* 'x 1 'y 2))                                ; xht + llama

 => '("x = 1" "y = 2")
;;; vector
(map-apply (lambda (i e) `(,i ,e)) [a b c])                 ; map
(-map-indexed (lambda (i e) `(,i ,e)) (append [a b c] ()))  ; dash
(--map-indexed `(,it-index ,it) (append [a b c] ()))        ; dash
(-map-indexed (-cut list <> <>) (append [a b c] ()))        ; dash
(-map-indexed (##list % %2) (append [a b c] ()))            ; dash + llama
(h-lmap (lambda (i e) `(,i ,e)) (h<-it [a b c]))            ; xht
(h-lmap (-cut list <> <>) (h<-it [a b c]))                  ; xht
(h--lmap (list key value) (h<-it [a b c]))                  ; xht
(h--lmap `(,key ,value) (h<-it [a b c]))                    ; xht
(h-lmap (##list % %2) (h<-it [a b c]))                      ; xht  + llama
(map-apply (-cut list <> <>) [a b c])                       ; map  + dash
(map-apply (##list % %2) [a b c])                           ; map  + llama
(h-items (h<-it [a b c]))                                   ; xht

 => '((0 a) (1 b) (2 c))

map-do

;;; alist
;;;; map
(let (result)
  (map-do (lambda (k v) (push (* k v) result))
          '((1 . 2) (3 . 4) (5 . 6)))
  (nreverse result))
=> '(2 12 30)

;;;; dash
(let (result)
  (-each '((1 . 2) (3 . 4) (5 . 6))
    (-lambda ((k . v)) (push (* k v) result)))
  (nreverse result))
=> '(2 12 30)

;;;; xht
(let (result)
  (h--each (h<-it '((1 . 2) (3 . 4) (5 . 6)))
    (push (* key value) result))
  (nreverse result))
=> '(2 12 30)

(-map (-compose #'-product #'-value-to-list) '((1 . 2) (3 . 4) (5 . 6))) => '(2 12 30) ; dash
(-map (-cut -> <> -value-to-list -product) '((1 . 2) (3 . 4) (5 . 6)))   => '(2 12 30) ; dash
(--map (-> it -value-to-list -product) '((1 . 2) (3 . 4) (5 . 6)))       => '(2 12 30) ; dash
(--map (-product (-value-to-list it)) '((1 . 2) (3 . 4) (5 . 6)))        => '(2 12 30) ; dash
(-map (-lambda ((k . v)) (* k v)) '((1 . 2) (3 . 4) (5 . 6)))            => '(2 12 30) ; dash
(h--lmap (* key value) (h<-it '((1 . 2) (3 . 4) (5 . 6))))               => '(2 12 30) ; xht
(h-lmap (-cut * <> <>) (h<-it '((1 . 2) (3 . 4) (5 . 6))))               => '(2 12 30) ; xht
(--map (* (car it) (cdr it)) '((1 . 2) (3 . 4) (5 . 6)))                 => '(2 12 30) ; dash
;;; hash table
;;;; map
(with-output-to-string
  (map-do (lambda (key value)
            (princ
             (format "%s = %s\n" (upcase key) value)))
          #s(hash-table data ("a" 1 "b" 2 "c" 3))))
=> "A = 1\nB = 2\nC = 3\n"

;;;; xht
(with-output-to-string
  (h--each (h* "a" 1 "b" 2 "c" 3)
    (princ (format "%s = %s\n" (upcase key) value))))
=> "A = 1\nB = 2\nC = 3\n"

map-keys

;;; alist
(map-keys '((a . 1) (b . ((c . 2)))))            => '(a b)  ; map
(ht-keys (ht<-alist '((a . 1) (b . ((c . 2)))))) => '(a b)  ; ht
(h-keys (h<-it '((a . 1) (b . ((c . 2))))))      => '(a b)  ; xht
(mapcar #'car '((a . 1) (b . ((c . 2)))))        => '(a b)  ; C src
(-map #'car '((a . 1) (b . ((c . 2)))))          => '(a b)  ; dash
;;; hash table
(map-keys #s(hash-table data (a 1 b #s(hash-table data (c 2))))) => '(a b)  ; map
(nreverse (ht-keys (ht ('a 1) ('b (ht ('c 2))))))                => '(a b)  ; ht
(h-keys (h* 'a 1 'b (h* 'c 2)))                                  => '(a b)  ; xht

map-keys-apply

;;; alist
(map-keys-apply #'identity '((a) (b) (c))) => '(a b c)  ; map
(ht-keys (ht<-alist '((a) (b) (c))))       => '(a b c)  ; ht
(h-keys (h<-alist '((a) (b) (c))))         => '(a b c)  ; xht
(--map (car it) '((a) (b) (c)))            => '(a b c)  ; dash
(mapcar #'car '((a) (b) (c)))              => '(a b c)  ; C src
(-map #'car '((a) (b) (c)))                => '(a b c)  ; dash
;;; alist
(map-keys-apply #'symbol-name '((a . 1) (b . 2)))          => '("a" "b")  ; map
(ht-amap (symbol-name key) (ht<-alist '((a . 1) (b . 2)))) => '("a" "b")  ; ht
(-map (-compose #'symbol-name #'car) '((a . 1) (b . 2)))   => '("a" "b")  ; dash
(h--lmap (symbol-name key) (h<-it '((a . 1) (b . 2))))     => '("a" "b")  ; xht
(-map (-cut -> <> car symbol-name) '((a . 1) (b . 2)))     => '("a" "b")  ; dash
(--map (symbol-name (car it)) '((a . 1) (b . 2)))          => '("a" "b")  ; dash
;;; hash table
(map-keys-apply #'symbol-name #s(hash-table data (a 1 b 2))) => '("a" "b")  ; map
(nreverse (ht-amap (symbol-name key) (ht ('a 1) ('b 2))))    => '("a" "b")  ; ht
(h--lmap (symbol-name key) (h* 'a 1 'b 2))                   => '("a" "b")  ; xht

map-values

;;; alist
(map-values '((a . 1) (b . 2)))            => '(1 2)  ; map
(ht-values (ht<-alist '((a . 1) (b . 2)))) => '(1 2)  ; ht
(h-values (h<-it '((a . 1) (b . 2))))      => '(1 2)  ; xht
(--map (cdr it) '((a . 1) (b . 2)))        => '(1 2)  ; dash
(mapcar #'cdr '((a . 1) (b . 2)))          => '(1 2)  ; C src
(-map #'cdr '((a . 1) (b . 2)))            => '(1 2)  ; dash
;;; hash table
(map-values #s(hash-table data (:a 1 :b 2))) => '(1 2)  ; map
(nreverse (ht-values (ht (:a 1) (:b 2))))    => '(1 2)  ; ht
(h-values (h* :a 1 :b 2))                    => '(1 2)  ; xht

map-values-apply

;;; alist
(map-values-apply #'1+ '((a . 1) (b . 2)))          => '(2 3)  ; map
(ht-amap (1+ value) (ht<-alist '((a . 1) (b . 2)))) => '(2 3)  ; ht
(h--lmap (1+ value) (h<-it '((a . 1) (b . 2))))     => '(2 3)  ; xht
(-map (-compose #'1+ #'cdr) '((a . 1) (b . 2)))     => '(2 3)  ; dash
(-map (-cut -> <> cdr 1+) '((a . 1) (b . 2)))       => '(2 3)  ; dash
(--map (1+ (cdr it)) '((a . 1) (b . 2)))            => '(2 3)  ; dash
;;; hash table
(map-values-apply #'1+ #s(hash-table data (:a 1 :b 2))) => '(2 3)  ; map
(nreverse (ht-amap (1+ value) (ht (:a 1) (:b 2))))      => '(2 3)  ; ht
(h--lmap (1+ value) (h* :a 1 :b 2))                     => '(2 3)  ; xht

Excerpting Maps

map-delete

;;; hash table
;;;; map
;; `setq' is necessary if you want to actually change `htbl'
(let ((htbl #s(hash-table data (:x 1 :y 2 :z 3))))
  (setq htbl (map-delete htbl :x))
  htbl)
H=> (h* :y 2 :z 3)

;;;; ht
(let ((htbl (ht (:x 1) (:y 2) (:z 3))))
  (ht-remove! htbl :x)
  htbl)
H=> (h* :y 2 :z 3)

;;;; xht
(let ((htbl (h* :x 1 :y 2 :z 3)))
  (h-rem! htbl :x)
  htbl)
H=> (h* :y 2 :z 3)
;;; hash table
;;;; map
;; `setq' is not used if you want the result without changing `htbl'
(let ((htbl #s(hash-table data (:x 1 :y 2 :z 3))))
  (map-delete htbl :x))
H=> (h* :y 2 :z 3)

;;;; ht
(let* ((htb1 (ht (:x 1) (:y 2) (:z 3)))
       (htb2 (ht-copy htb1)))
  (ht-remove! htb2 :x)
  htb2)
H=> (h* :y 2 :z 3)

;;;; xht
(let ((htbl (h* :x 1 :y 2 :z 3)))
  (h-rem htbl :x))
H=> (h* :y 2 :z 3)
;;; alist
;;;; map
;; `setq' is necessary if you want to actually change `alist'
(let ((alist '((x . 1) (y . 2) (z . 3))))
  (setq alist (map-delete alist 'x))
  alist)
=> '((y . 2) (z . 3))

;;;; ht
(let* ((alist '((x . 1) (y . 2) (z . 3)))
       (htble (ht<-alist alist)))
  (ht-remove! htble 'x)
  (setq alist (ht->alist htble))
  alist)
=> '((y . 2) (z . 3))

;;;; xht
(let ((alist '((x . 1) (y . 2) (z . 3))))
  (setq alist (-> alist h<-it (h-rem 'x) h->alist))
  alist)
=> '((y . 2) (z . 3))
;;; alist
;;;; map
;; `setq' is not used if you want the result without changing `alist'
(let ((alist '((x . 1) (y . 2) (z . 3))))
  (map-delete alist 'x))
=> '((y . 2) (z . 3))

;;;; ht
(let* ((alist '((x . 1) (y . 2) (z . 3)))
       (htble (ht<-alist alist)))
  (ht-remove! htble 'x)
  (ht->alist htble))
=> '((y . 2) (z . 3))

;;;; xht
(let ((alist '((x . 1) (y . 2) (z . 3))))
  (-> alist h<-it (h-rem 'x) h->alist))
=> '((y . 2) (z . 3))

map-filter

Note that map-filter always returns an alist.

;;; hash table → alist
(map-filter (lambda (_k v) (> v 2)) #s(hash-table data (:a 1 :b 2 :c 3)))                    ; map
(-non-nil (->> (ht (:a 1) (:b 2) (:c 3)) (ht-map (lambda (k v) (and (> v 2) `(,k . ,v))))))  ; ht
(-non-nil (->> (ht (:a 1) (:b 2) (:c 3)) (ht-amap (and (> value 2) (cons key value)))))      ; ht
(-non-nil (->> (ht (:a 1) (:b 2) (:c 3)) (ht-map (##and (> %2 2) `(,%1 . ,%2)))))            ; ht + llama
(-non-nil (->> (ht (:a 1) (:b 2) (:c 3)) (ht-map (-andfn (##> %2 2) #'cons))))               ; ht + llama
(->> (ht (:a 1) (:b 2) (:c 3)) (ht-select (lambda (_k v) (> v 2))) ht->alist)                ; ht
(->> (h* :a 1 :b 2 :c 3) (h-lkeep (lambda (k v) (and (> v 2) `(,k . ,v)))))                  ; xht
(->> (h* :a 1 :b 2 :c 3) (h--lkeep (and (> value 2) (cons key value))))                      ; xht
(->> (ht (:a 1) (:b 2) (:c 3)) ht->alist (--filter (> (cdr it) 2)))                          ; ht
(->> (h* :a 1 :b 2 :c 3) (h-sel (lambda (_k v) (> v 2))) h->alist)                           ; xht
(->> (ht (:a 1) (:b 2) (:c 3)) (ht-select (##> %2 2)) ht->alist)                             ; ht  + llama
(->> (h* :a 1 :b 2 :c 3) (h-lkeep (-andfn (##> %2 2) #'cons)))                               ; xht + llama
(map-filter (##> %2 2) #s(hash-table data (:a 1 :b 2 :c 3)))                                 ; map + llama
(->> (h* :a 1 :b 2 :c 3) h->alist (--filter (> (cdr it) 2)))                                 ; xht
(->> (h* :a 1 :b 2 :c 3) (h--sel (> value 2)) h->alist)                                      ; xht
(->> (h* :a 1 :b 2 :c 3) (h-sel (##> %2 2)) h->alist)                                        ; xht + llama

=> '((:c . 3))

The previous note about readability also applies here, so the last pure xht version seems preferable.

;;; vector → alist
(map-filter (lambda (i _e) (cl-evenp i)) [a b c d])                           ; map
(->> [a b c d] h<-it (h-lkeep (lambda (k v) (and (cl-evenp k) `(,k . ,v)))))  ; xht
(->> [a b c d] h<-it (h--lkeep (and (cl-evenp key) (cons key value))))        ; xht
(->> [a b c d] h<-it h->alist (-filter (-compose #'cl-evenp #'car)))          ; xht
(->> [a b c d] h<-it (h-sel (lambda (k _v) (cl-evenp k))) h->alist)           ; xht
(->> [a b c d] h<-it h->alist (-filter (-cut -> <> car cl-evenp)))            ; xht
(->> [a b c d] h<-it (h-lkeep (-andfn (##cl-evenp % _%2) #'cons)))            ; xht + llama
(->> [a b c d] h<-it (h-lkeep (##and (cl-evenp %) `(,% . ,%2))))              ; xht + llama
(->> [a b c d] h<-it h->alist (--filter (cl-evenp (car it))))                 ; xht
(->> [a b c d] h<-it (h-sel (##cl-evenp % _%2)) h->alist)                     ; xht + llama
(->> [a b c d] h<-it (h--sel (cl-evenp key)) h->alist)                        ; xht
(map-filter (##cl-evenp % _%2) [a b c d])                                     ; map + llama

=> '((0 . a) (2 . c))

map-remove

Note that map-remove always returns an alist.

;;; hash table → alist
(map-remove (lambda (_k v) (>= v 2)) #s(hash-table data (:a 1 :b 2 :c 3)))                       ; map
(-non-nil (->> (ht (:a 1) (:b 2) (:c 3)) (ht-map (lambda (k v) (unless (>= v 2) `(,k . ,v))))))  ; ht
(-non-nil (->> (ht (:a 1) (:b 2) (:c 3)) (ht-amap (unless (>= value 2) (cons key value)))))      ; ht
(-non-nil (->> (ht (:a 1) (:b 2) (:c 3)) (ht-map (##unless (>= %2 2) `(,% . ,%2)))))             ; ht  + llama
(->> (h* :a 1 :b 2 :c 3) (h-lkeep (lambda (k v) (unless (>= v 2) `(,k . ,v)))))                  ; xht
(->> (ht (:a 1) (:b 2) (:c 3)) (ht-reject (lambda (_k v) (>= v 2))) ht->alist)                   ; ht
(->> (h* :a 1 :b 2 :c 3) (h--lkeep (unless (>= value 2) (cons key value))))                      ; xht
(->> (ht (:a 1) (:b 2) (:c 3)) ht->alist (-remove (-cut -> <> cdr (>= 2))))                      ; ht
(->> (ht (:a 1) (:b 2) (:c 3)) ht->alist (-remove (##>= (cdr %) 2)))                             ; ht  + llama
(->> (ht (:a 1) (:b 2) (:c 3)) ht->alist (--remove (>= (cdr it) 2)))                             ; ht
(->> (h* :a 1 :b 2 :c 3) (h-lkeep (##unless (>= %2 2) `(,% . ,%2))))                             ; xht + llama
(->> (h* :a 1 :b 2 :c 3) h->alist (-remove (-cut -> <> cdr (>= 2))))                             ; xht
(->> (h* :a 1 :b 2 :c 3) (h-rej (lambda (_k v) (>= v 2))) h->alist)                              ; xht
(->> (ht (:a 1) (:b 2) (:c 3)) (ht-reject (##>= %2 2)) ht->alist)                                ; ht  + llama
(map-remove (##>= %2 2) #s(hash-table data (:a 1 :b 2 :c 3)))                                    ; map + llama
(->> (h* :a 1 :b 2 :c 3) h->alist (-remove (##>= (cdr %) 2)))                                    ; xht + llama
(->> (h* :a 1 :b 2 :c 3) h->alist (--remove (>= (cdr it) 2)))                                    ; xht
(->> (h* :a 1 :b 2 :c 3) (h--rej (>= value 2)) h->alist)                                         ; xht
(->> (h* :a 1 :b 2 :c 3) (h-rej (##>= %2 2)) h->alist)                                           ; xht + llama

=> '((:a . 1))

The previous note about readability also applies here, so the last pure xht version seems preferable.

;;; vector → alist
(map-remove (lambda (i _e) (cl-evenp i)) [a b c d])                              ; map
(->> [a b c d] h<-it (h-lkeep (lambda (i e) (unless (cl-evenp i) `(,i . ,e)))))  ; xht
(->> [a b c d] h<-it (h--lkeep (unless (cl-evenp key) (cons key value))))        ; xht
(->> [a b c d] h<-it (h-lkeep (##unless (cl-evenp %) `(,% . ,%2))))              ; xht + llama
(->> [a b c d] h<-it (h-rej (lambda (i _e) (cl-evenp i))) h->alist)              ; xht
(->> [a b c d] h<-it h->alist (-reject (-cut -> <> car cl-evenp)))               ; xht
(->> [a b c d] h<-it h->alist (-reject (##cl-evenp (car %))))                    ; xht + llama
(->> [a b c d] h<-it h->alist (--reject (cl-evenp (car it))))                    ; xht
(->> [a b c d] h<-it (h-rej (##cl-evenp % _%2)) h->alist)                        ; xht + llama
(->> [a b c d] h<-it (h--rej (cl-evenp key)) h->alist)                           ; xht
(map-remove (##cl-evenp % _%2) [a b c d])                                        ; map + llama

=> '((1 . b) (3 . d))

map-merge

;;; inputs of different types, alist output
(map-merge 'list #s(hash-table data (x 1 y 2)) '((z . 3)))                       ; map
(funcall (-on (-compose #'h->alist #'h-mix) #'h<-it) (h* 'x 1 'y 2) '((z . 3)))  ; xht
(h->alist (apply #'h-mix (-map #'h<-it `(,(h* 'x 1 'y 2) ((z . 3))))))           ; xht
(h->alist (funcall (-on #'h-mix #'h<-it) (h* 'x 1 'y 2) '((z . 3))))             ; xht

 => '((x . 1) (y . 2) (z . 3))
;;; inputs of different types, hash table output
(map-merge 'hash-table #s(hash-table data (x 1 y 2)) '((z . 3)))  ; map
(apply #'h-mix (-map #'h<-it `(,(h* 'x 1 'y 2) ((z . 3)))))       ; xht
(funcall (-on #'h-mix #'h<-it) (h* 'x 1 'y 2) '((z . 3)))         ; xht

H=> (h* 'x 1 'y 2 'z 3)
;;; hash table inputs, alist output
(map-merge 'list #s(hash-table data (x 1 y 2)) #s(hash-table data (z 3)))  ; map
(h->alist (h-mix (h* 'x 1 'y 2) (h* 'z 3)))                                ; xht

=> '((x . 1) (y . 2) (z . 3))
;;; hash table inputs, hash table output
(map-merge 'hash-table #s(hash-table data (x 1 y 2)) #s(hash-table data (z 3)))  ; map
(h-mix (h* 'x 1 'y 2) (h* 'z 3))                                                 ; xht

H=> (h* 'x 1 'y 2 'z 3)

map-merge-with

;;;; map
(map-merge-with 'hash-table
                (lambda (v1 v2) (list v1 v2))
                #s(hash-table data (x 1 y 2))
                '((x . 3) (y . 4) (z . 5)))
H=> (h* 'x '(1 3)
        'y '(2 4)
        'z 5)

(map-merge-with 'list
                (lambda (v1 v2) (list v1 v2))
                #s(hash-table data (x 1 y 2))
                '((x . 3) (y . 4) (z . 5)))
=> '((x 1 3)
     (y 2 4)
     (z . 5))
;;;; xht
;; Let's define a temporary function that does the above
(defun h-_merge-with (&rest maps)
  (let ((res (h-new)))
    (dolist (map (-map #'h<-it (nreverse maps)) res)
      (h--each map (h-put-add! res key value)))))

;; And then:
(h-_merge-with (h* 'x 1 'y 2) '((x . 3) (y . 4) (z . 5)))
H=> (h* 'x '(1 3)
        'y '(2 4)
        'z 5)

(h->alist
 (h-_merge-with (h* 'x 1 'y 2) '((x . 3) (y . 4) (z . 5))))
=> '((x 1 3)
     (y 2 4)
     (z . 5))

map-insert

;;; hash table
(map-insert #s(hash-table data (x 1 y 2)) 'z 3)         H=> (h* 'x 1 'y 2 'z 3)  ; map
(let ((tbl (ht ('x 1) ('y 2)))) (ht-set! tbl 'z 3) tbl) H=> (h* 'x 1 'y 2 'z 3)  ; ht
(h-put (h* 'x 1 'y 2) 'z 3)                             H=> (h* 'x 1 'y 2 'z 3)  ; xht
;; ^non-destructive
;;; alist (new pair)
(map-insert '((x . 1) (y . 2)) 'z 3)                => '((z . 3) (x . 1) (y . 2))  ; map
(-> '((x . 1) (y . 2)) h<-it (h-put 'z 3) h->alist) => '((x . 1) (y . 2) (z . 3))  ; xht
;;; alist (update pair)
(map-insert '((x . 1) (y . 2)) 'y 5)                => '((y . 5) (x . 1))  ; map
(-> '((x . 1) (y . 2)) h<-it (h-put 'y 5) h->alist) => '((x . 1) (y . 5))  ; xht

map-put!

;;; hash table
(let ((a #s(hash-table data (x 1 y 2)))) (map-put! a 'z 3) a) H=> (h* 'x 1 'y 2 'z 3)  ; map
(let ((a (ht ('x 1) ('y 2)))) (ht-set! a 'z 3) a)             H=> (h* 'x 1 'y 2 'z 3)  ; ht
(let ((a (h* 'x 1 'y 2))) (h-put! a 'z 3) a)                  H=> (h* 'x 1 'y 2 'z 3)  ; xht

Binding

map-let

;;; hash table
(map-let (('one x) ('thr z)) #s(hash-table data (one 1 two 2 thr 3)) (list x z))  => '(1 3)  ; map
(-let [(&hash 'one x 'thr z) #s(hash-table data (one 1 two 2 thr 3))] (list x z)) => '(1 3)  ; dash
(-let [(&hash 'one 'thr) #s(hash-table data (one 1 two 2 thr 3))] (list one thr)) => '(1 3)  ; dash
(map-let (one thr) #s(hash-table data (one 1 two 2 thr 3)) (list one thr))        => '(1 3)  ; map
(h-let (h* 'one 1 'two 2 'thr 3) (list .one .thr))                                => '(1 3)  ; xht
;;; alist
(map-let (('one x) ('thr z)) '((one . 1) (two . 2) (thr . 3)) (list x z))   => '(1 3)  ; map
(-let [(&alist 'one x 'thr z) '((one . 1) (two . 2) (thr . 3))] (list x z)) => '(1 3)  ; dash
(-let [(&alist 'one 'thr) '((one . 1) (two . 2) (thr . 3))] (list one thr)) => '(1 3)  ; dash
(-let [(x _ z) '((one . 1) (two . 2) (thr . 3))] (list (cdr x) (cdr z)))    => '(1 3)  ; dash
(map-let (one thr) '((one . 1) (two . 2) (thr . 3)) (list one thr))         => '(1 3)  ; map
(let-alist '((one . 1) (two . 2) (thr . 3)) (list .one .thr))               => '(1 3)  ; let-alist
(h-let-it '((one . 1) (two . 2) (thr . 3)) (list .one .thr))                => '(1 3)  ; xht
;;; plist
(map-let (('one x) ('thr z)) '(one 1 two 2 thr 3) (list x z))   => '(1 3)  ; map
(-let [(&plist 'one x 'thr z) '(one 1 two 2 thr 3)] (list x z)) => '(1 3)  ; dash
(-let [(&plist 'one 'thr) '(one 1 two 2 thr 3)] (list one thr)) => '(1 3)  ; dash
(map-let (one thr) '(one 1 two 2 thr 3) (list one thr))         => '(1 3)  ; map
(h-let-it '(one 1 two 2 thr 3) (list .one .thr))                => '(1 3)  ; xht

Closing thoughts

All of these are fine libraries with pros and cons.

Enjoy the many available options we have.

Source of native functions

I wrote in the beginning:

;;;; all else   (native)
(let-alist a x)   ; let-alist
(foo else)        ; C source code, subr, or subr-x

Here are the details for that:

;;;; C source code (primitives), subr, or subr-x
=> (list
    "subr-x" '(hash-table-empty-p)
    "subr"   '(alist-get dolist lambda plistp push)
    "C src"  '(/ = and apply aref car cdr cons copy-alist copy-hash-table
                 copy-sequence elt expt funcall gethash hash-table-count
                 hash-table-p length let let* list mapcar nreverse
                 plist-get setq))

Either of the below will get you (roughly) the above:

;;;;; dash solution
(let* ((funs '(/ = alist-get and apply aref car cdr cons copy-alist
                 copy-hash-table copy-sequence dolist elt expt funcall gethash
                 hash-table-count hash-table-empty-p hash-table-p lambda length
                 let let* list mapcar nreverse plist-get plistp push setq))
       (libs  (--group-by (if-let ((file (symbol-file it)))
                              (file-name-base file)
                            "C src")  ; C source code = primitive
                          funs)))
  (-mapcat (-lambda ((a . b)) `(,a ,b)) (nreverse libs)))
;;;;; xht solution
(let* ((funs '(/ = alist-get and apply aref car cdr cons copy-alist
                 copy-hash-table copy-sequence dolist elt expt funcall gethash
                 hash-table-count hash-table-empty-p hash-table-p lambda length
                 let let* list mapcar nreverse plist-get plistp push setq))
       (htbl  (h-new (length funs))))
  (dolist (fun (nreverse funs))
    (h-put-add! htbl (if-let ((file (symbol-file fun)))
                         (file-name-base file)
                       "C src")  ; C source code = primitive
                fun))
  (h->plist htbl))

Acknowledgments

;; I wrote the non-map solutions, plus several map usage examples
;; SPDX-FileCopyrightText:  © flandrew
;;
;; Many other map usage examples came from elisp-demos
;; SPDX-FileCopyrightText:  © 2018-2020 Xu Chunyang
;;
;; All is under the same license
;; SPDX-License-Identifier: GPL-3.0-or-later

See also

If you liked this, you may want to see the same idea applied to seq.el.