seq.el vs. other libraries through hundreds of examples

If you code in Emacs Lisp, you must deal with lists.

To get the output you want, these lists must be consed, copied, cloned, parsed, mapped, mixed, zipped, sorted, updated, unfolded, consulted, partitioned, intersected, appended, extended, reduced, reversed, splayed, spliced, sliced, and split. The documentation on splintering is scarse, and there's still no secure way to splash them — but for every other operation imaginable, there's a large list of useful functions available for you to select, study, and splurge on.

Today we're going to compare a subset of these 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.

seq.el

This article is centered on seq.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, most of which can deal not only with lists but also with other sequence types.
  • Last, but not list least, because I never seem to use it. In all my public Emacs packages, I see only one seq function — a single use of a seq-position.

So what do I use instead?

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

To answer these questions, I:

  • listed seq.el's functions,
  • collected usage examples of each, sometimes adding examples of my own,
  • and, for each seq 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.

seq.el's functions and their alternatives

A summary of where the functions come from:

;;; Libraries used here

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

;;;; 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
(s-foo x y)       ; s
(##foo % %2)      ; llama

;;;; all else   (native)
(pcase-foo x)     ; pcase
(foo else)        ; C source code, subr, subr-x, simple, or cl-macs

For each seq function:

  • I pick one or more examples, always showing a seq 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.

Sequence Predicates

seqp

;;; list
(seqp '(a b c))      => t  ; seq
(sequencep '(a b c)) => t  ; C src
;;; vector
(seqp [a b c])      => t  ; seq
(sequencep [a b c]) => t  ; C src
;;; string
(seqp "a b c")      => t  ; seq
(sequencep "a b c") => t  ; C src

seq-contains-p

(seq-contains-p '(a b c) 'b)     => t       ; seq
(and (-contains? '(a b c) 'b) t) => t       ; dash
(-contains? '(a b c) 'b)         => '(b c)  ; dash
(member 'b '(a b c))             => '(b c)  ; C src
(memq 'b '(a b c))               => '(b c)  ; C src
(seq-contains-p '(a b c) 'd)     => nil  ; seq
(and (-contains? '(a b c) 'd) t) => nil  ; dash
(-contains? '(a b c) 'd)         => nil  ; dash
(member 'd '(a b c))             => nil  ; C src
(memq 'd '(a b c))               => nil  ; C src

seq-empty-p

;;; vector
(seq-empty-p [])    => t  ; seq
(zerop (length [])) => t  ; subr
(= 0 (length []))   => t  ; C src (faster than #'zerop)
(h-it-empty? [])    => t  ; xht
;;; list
(seq-empty-p ())    => t  ; seq
(zerop (length ())) => t  ; subr
(= 0 (length ()))   => t  ; C src
(h-it-empty? ())    => t  ; xht
(null ())           => t  ; C src
;;; string
(seq-empty-p "")     => t  ; seq
(string-equal "" "") => t  ; C src
(string-empty-p "")  => t  ; simple
(zerop (length ""))  => t  ; subr
(= 0 (length ""))    => t  ; C srcs
(h-it-empty? "")     => t  ; xht
(string= "" "")      => t  ; C src
(s-blank? "")        => t  ; s

There's a bit of a special case with nil:

;;; nil
(seq-empty-p nil) => t  ; seq
(h-it-empty? nil) => t  ; xht
(s-blank? nil)    => t  ; s (would error if arg is not string or nil)

seq-every-p

(seq-every-p #'numberp '(1 2 3)) => t  ; seq
(-every-p #'numberp '(1 2 3))    => t  ; dash
(-every? #'numberp '(1 2 3))     => t  ; dash
(-all? #'numberp '(1 2 3))       => t  ; dash

seq-some

(seq-some #'floatp '(1 2.0 3)) => t  ; seq
(-some #'floatp '(1 2.0 3))    => t  ; dash

seq-set-equal-p

lists
;;; lists
(seq-set-equal-p '(1 2 3) '(3 1 2))                      => t  ; seq
(h= (h-as-set<-list '(1 2 3)) (h-as-set<-list '(3 1 2))) => t  ; xht
(apply #'h= (-map #'h-as-set<-list '((1 2 3) (3 1 2))))  => t  ; xht
(-same-items? '(1 2 3) '(3 1 2))                         => t  ; dash

The dash solution above, for lists, is the shortest, and as straightforward as seq's. This pattern happens many times throughout this article.

vectors

But for vectors seq usually has an advantage, since not many dash functions can deal directly with them. So dash needs to convert them to lists first, either with (append VECTOR ()) or with (--map it VECTOR). This makes dash solutions for vectors (and strings) longer and more complex.

;;; vectors
;;;; given as two separate vectors
(seq-set-equal-p [1 2 3] [3 1 2 1])                                  => t  ; seq
(funcall (-on #'-same-items? (-cut append <> ())) [1 2 3] [3 1 2 1]) => t  ; dash
(funcall (-on #'-same-items? (##append % ())) [1 2 3] [3 1 2 1])     => t  ; dash + llama
(h= (h-as-set<-vector [1 2 3]) (h-as-set<-vector [3 1 2 1]))         => t  ; xht
(-same-items? (append [1 2 3] ()) (append [3 1 2 1] ()))             => t  ; dash
(-same-items? (--map it [1 2 3]) (--map it [3 1 2 1]))               => t  ; dash

See what I mean?

It gets worse:

;;;; given as a list of two vectors
(apply #'seq-set-equal-p '([1 2 3] [3 1 2 1]))                                                          ; seq
(not (null (--reduce (when (-same-items? it acc) it) (-map (-cut append <> ()) '([1 2 3] [3 1 2 1]))))) ; dash
(not (null (--reduce (when (-same-items? it acc) it) (--map (append it ()) '([1 2 3] [3 1 2 1])))))     ; dash
(not (null (--reduce (when (-same-items? it acc) it) (-map (##append % ()) '([1 2 3] [3 1 2 1])))))     ; dash + llama
(-all? (-lambda ((a b)) (seq-set-equal-p a b)) (-partition-in-steps 2 1 '([1 2 3] [3 1 2 1])))          ; dash + seq
(--all? (seq-set-equal-p (car it) (cadr it)) (-partition-in-steps 2 1 '([1 2 3] [3 1 2 1])))            ; dash + seq
(not (null (--reduce (when (seq-set-equal-p it acc) it) '([1 2 3] [3 1 2 1]))))                         ; dash + seq
(--tree-mapreduce (append it ()) (-same-items? it acc) '([1 2 3] [3 1 2 1]))                            ; dash
(-tree-mapreduce (-cut append <> ()) #'-same-items? '([1 2 3] [3 1 2 1]))                               ; dash
(apply #'-same-items? (-map (-cut append <> ()) '([1 2 3] [3 1 2 1])))                                  ; dash
(-tree-mapreduce (##append % ()) #'-same-items? '([1 2 3] [3 1 2 1]))                                   ; dash + llama
(apply #'-same-items? (--map (append it ()) '([1 2 3] [3 1 2 1])))                                      ; dash
(apply #'-same-items? (-map (##append % ()) '([1 2 3] [3 1 2 1])))                                      ; dash + llama
(apply #'h= (-map #'h-as-set<-vector '([1 2 3] [3 1 2 1])))                                             ; xht

=> t

I simplify. There are 12 #'-same-items? combinations involving -reduce × -map:

--?reduce explicit lambda, anaphoric, llama
--?map explicit lambda, anaphoric, llama, -cut

(There would be 16 if -cut could be made to work with when.)

I show only 3 of these, at the top, namely: anaphoric × '(-cut anaphoric llama).

The largest one would look like this:

(not (null (-reduce (lambda (a i) (when (-same-items? a i) i)) (-map (lambda (x) (append x nil)) '([1 2 3] [3 1 2 1])))))

These larger ones are here for educational purposes only. The ones I'd pick would be either the shortest pure dash or the xht one at the bottom.

Alternatives to not null

What is that (not (null…)) thing above, by the way? As in here:

(not (null (--reduce (when (-same-items? it acc) it) (--map (append it ()) '([1 2 3] [3 1 2 1]))))) => t

That's because without it we get this:

(--reduce (when (-same-items? it acc) it) (--map (append it ()) '([1 2 3] [3 1 2 1]))) => '(3 1 2 1)
(--reduce (when (-same-items? it acc) it) (--map (append it ()) '([1 2 3] [4 4 1])))   => nil

So the nil must remain nil, and any non-nil value must become t.

An alternative is to use (and … t). That would have reduced the length of these previous solutions by five characters, which is good.

Its drawback is that it pushes a t to the end. Compare:

(not (null (--reduce (when (-same-items? it acc) it) (--map (append it ()) '([1 2 3] [3 1 2 1]))))) => t
(and (--reduce (when (-same-items? it acc) it) (--map (append it ()) '([1 2 3] [3 1 2 1]))) t)      => t
Two more

Let's wrap this up with two solutions more. They mimic the algorithm used by the definition of seq-set-equal-p itself:

;;;; pcase + seq + llama
(pcase-let ((`(,v1 ,v2) '([1 2 3] [3 1 2 1])))
  (and (seq-every-p (##seq-contains-p v2 %) v1)
       (seq-every-p (##seq-contains-p v1 %) v2)))
=> t
;;;; dash
(-let [(s1 s2) (--map (append it ()) '([1 2 3] [3 1 2 1]))]
  (and (--all? (-contains? s2 it) s1)
       (--all? (-contains? s1 it) s2)))
=> t
strings
;;; strings
;;;; given as two separate strings
(seq-set-equal-p "good" "dog")                                  => t  ; seq
(funcall (-on #'-same-items? (-cut append <> ())) "good" "dog") => t  ; dash
(funcall (-on #'-same-items? (##append % ())) "good" "dog")     => t  ; dash + llama
(h= (h-as-set<-vector "good") (h-as-set<-vector "dog"))         => t  ; xht
(-same-items? (append "good" ()) (append "dog" ()))             => t  ; dash
(-same-items? (--map it "good") (--map it "dog"))               => t  ; dash

and so on, since the solutions for strings and for vectors look alike.

But all of that is for when we have two sets as input. What if we had more?

A note on arity

To check whether two sets have the same items, these functions are used:

library function arity
seq seq-set-equal-p 2
dash -same-items? 2
xht h= ≥2

As a consequence, the solution using the variadic #'h= is likely to be the shortest when comparing more than two sets.

Let's see that.

An example with three items — and different ways to do recursion in Emacs Lisp

In roughly decreasing order of length.

Here seq and dash play throw and catch:

;;;; seq
(let ((l '((a c t) (c a t) (t a c t))))
  (catch 'break
    (while (cdr l)
      (if (seq-set-equal-p (car l) (cadr l))
          (setq l (cdr l))
        (throw 'break nil)))
    t))
=> t
;;;; dash
(let ((l '((a c t) (c a t) (t a c t))))
  (catch 'break
    (while (cdr l)
      (if (-same-items? (car l) (cadr l))
          (!cdr l)  ; dash's destructive cdr
        (throw 'break nil)))
    t))
=> t

With recursive binding:

;;;; seq + subr
(letrec ((set= (lambda (l)
                 (if (cdr l)
                     (and (seq-set-equal-p (car l) (cadr l))
                          (funcall set= (cdr l)))
                   t))))
  (funcall set= '((a c t) (c a t) (t a c t))))
=> t
;;;; seq + subr + llama
(letrec ((set= (##if (cdr %)
                   (and (seq-set-equal-p (car %) (cadr %))
                        (funcall set= (cdr %)))
                 t)))
  (funcall set= '((a c t) (c a t) (t a c t))))
=> t
;;;; seq + cl-macs
(cl-labels ((set= (l)
              (if (cdr l)
                  (and (seq-set-equal-p (car l) (cadr l))
                       (set= (cdr l)))
                t)))
  (set= '((a c t) (c a t) (t a c t))))
=> t

With recursive body — the shortest solution here using only native libraries:

;;;; seq + subr-x
(named-let set= ((l '((a c t) (c a t) (t a c t))))
  (if (cdr l)
      (and (seq-set-equal-p (car l) (cadr l))
           (set= (cdr l)))
    t))
=> t

Abusing dash's fixed-point iteration — anaphoric edition:

;;;; dash
(car (--fix (if (cdr it)
                (and (-same-items? (car it) (cadr it))
                     (cdr it))
              (and it '(t)))
            '((a c t) (c a t) (t a c t))))
=> t

But some dash combinations can make it shorter:

;;;; dash + seq
(-all? (-lambda ((a b)) (seq-set-equal-p a b)) (-partition-in-steps 2 1 '((a c t) (c a t) (t a c t)))) => t
(--all? (seq-set-equal-p (car it) (cadr it)) (-partition-in-steps 2 1 '((a c t) (c a t) (t a c t))))   => t

In fact, much shorter:

;;;; dash + seq
(and (--reduce (and (seq-set-equal-p it acc) it) '((a c t) (c a t) (t a c t))) t) => t
;;;; dash
(and (--reduce (and (-same-items? it acc) it) '((a c t) (c a t) (t a c t))) t)    => t
;;;; dash + llama
(and (-reduce (##and (-same-items? % %2) %) '((a c t) (c a t) (t a c t))) t)      => t

But here's the shortest:

;;;; xht
(apply #'h= (-map #'h-as-set<-list '((a c t) (c a t) (t a c t)))) => t

Building Sequences

seq-concatenate

;;; lists → vector
(seq-concatenate 'vector '(1 2) '(c d)) => [1 2 c d]  ; seq
(vconcat '(1 2) '(c d))                 => [1 2 c d]  ; C src
;;; lists → string
(seq-concatenate 'string '(?a ?b) '(?c ?d)) => "abcd"  ; seq
(concat '(?a ?b) '(?c ?d))                  => "abcd"  ; C src
;;; strings → string
(seq-concatenate 'string "ab" "cd") => "abcd"  ; seq
(concat "ab" "cd")                  => "abcd"  ; C src
;;; mixed → list
(seq-concatenate 'list '(1 2) '(3 4) [5 6])   => '(1 2 3 4 5 6)  ; seq
(string-to-list (concat '(1 2) '(3 4) [5 6])) => '(1 2 3 4 5 6)  ; subr + C src
(append '(1 2) '(3 4) [5 6] ())               => '(1 2 3 4 5 6)  ; C src

seq-copy

(seq-copy '(a 2))      => '(a 2)  ; seq
(copy-sequence '(a 2)) => '(a 2)  ; C src
(-copy '(a 2))         => '(a 2)  ; dash

seq-into

;;; list → vector
(seq-into '(1 2 3) 'vector) => [1 2 3]  ; seq
(vconcat '(1 2 3))          => [1 2 3]  ; C src
;;; vector → list
(seq-into [a b c] 'list)    => '(a b c)  ; seq
(mapcar #'identity [a b c]) => '(a b c)  ; C src
(-map #'identity [a b c])   => '(a b c)  ; dash
(string-to-list [a b c])    => '(a b c)  ; subr
(append [a b c] ())         => '(a b c)  ; C src
(--map it [a b c])          => '(a b c)  ; dash

Utility Functions

seq-count

;;; list
(seq-count #'numberp '(1 b c 4)) => 2  ; seq
(-count #'numberp '(1 b c 4))    => 2  ; dash
;;; list
(seq-count (lambda (elt) (> elt 0)) '(-1 2 0 3 -2)) => 2  ; seq
(-count (lambda (elt) (> elt 0)) '(-1 2 0 3 -2))    => 2  ; dash
(seq-count (-cut > <> 0) '(-1 2 0 3 -2))            => 2  ; seq  + dash
(-count (-cut > <> 0) '(-1 2 0 3 -2))               => 2  ; dash
(seq-count (##> % 0) '(-1 2 0 3 -2))                => 2  ; seq  + llama
(-count (##> % 0) '(-1 2 0 3 -2))                   => 2  ; dash + llama
(--count (> it 0) '(-1 2 0 3 -2))                   => 2  ; dash
;;; vector
(seq-count (lambda (elt) (> elt 0)) [-1 2 0 3 -2])  => 2  ; seq
(-count (-cut > <> 0) (append [-1 2 0 3 -2] ()))    => 2  ; dash
(-count (-cut > <> 0) (--map it [-1 2 0 3 -2]))     => 2  ; dash
(-count (##> % 0) (append [-1 2 0 3 -2] ()))        => 2  ; dash + llama
(--count (> it 0) (append [-1 2 0 3 -2] ()))        => 2  ; dash
(-count (##> % 0) (--map it [-1 2 0 3 -2]))         => 2  ; dash + llama
(--count (> it 0) (--map it [-1 2 0 3 -2]))         => 2  ; dash
(seq-count (-cut > <> 0) [-1 2 0 3 -2])             => 2  ; seq  + dash
(seq-count (##> % 0) [-1 2 0 3 -2])                 => 2  ; seq  + llama

seq-elt

(seq-elt '(a b c) 1) => b  ; seq
(elt '(a b c) 1)     => b  ; C src
(nth 1 '(a b c))     => b  ; C src

seq-random-elt

;;; list
(seq-random-elt '(a b c d))                      => c ;e.g.  ; seq
(-> '(a b c d) h<-list h-random cadar)           => c ;e.g.  ; xht
(elt (-cycle '(a b c d)) (abs (random)))         => c ;e.g.  ; dash
(nth (abs (random)) (-cycle '(a b c d)))         => c ;e.g.  ; dash
(-> '(a b c d) h<-list h-pop-random cadr)        => c ;e.g.  ; xht
(--> '(a b c d) (nth (random (length it)) it))   => c ;e.g.  ; dash
(--> '(a b c d) (elt it (random (length it))))   => c ;e.g.  ; dash
(-as-> '(a b c d) s (nth (random (length s)) s)) => c ;e.g.  ; dash
(-as-> '(a b c d) s (elt s (random (length s)))) => c ;e.g.  ; dash
(let* ((s '(a b c d))
       (l (length s))
       (r (random l)))
  (nth r s))
=> c  ;e.g. ; C src
;;; vector
(seq-random-elt [a b c d])                       => c ;e.g.  ; seq
(elt (-cycle [a b c d]) (abs (random)))          => c ;e.g.  ; dash
(nth (abs (random)) (-cycle [a b c d]))          => c ;e.g.  ; dash
(-> [a b c d] h<-vector h-random cadar)          => c ;e.g.  ; xht
(-> [a b c d] h<-vector h-pop-random cadr)       => c ;e.g.  ; xht
(--> [a b c d] (elt it (random (length it))))    => c ;e.g.  ; dash
(--> [a b c d] (aref it (random (length it))))   => c ;e.g.  ; dash
(-as-> [a b c d] s (elt s (random (length s))))  => c ;e.g.  ; dash
(-as-> [a b c d] s (aref s (random (length s)))) => c ;e.g.  ; dash
(let* ((s [a b c d])
       (l (length s))
       (r (random l)))
  (aref s r))
=> c  ;e.g. ; C src
;;; string
(seq-random-elt "abcd")                       => ?c ;e.g.  ; seq
(elt (-cycle "abcd") (abs (random)))          => ?c ;e.g.  ; dash
(nth (abs (random)) (-cycle "abcd"))          => ?c ;e.g.  ; dash
(-> "abcd" h<-vector h-random cadar)          => ?c ;e.g.  ; xht
(-> "abcd" h<-vector h-pop-random cadr)       => ?c ;e.g.  ; xht
(--> "abcd" (elt it (random (length it))))    => ?c ;e.g.  ; dash
(--> "abcd" (aref it (random (length it))))   => ?c ;e.g.  ; dash
(-as-> "abcd" s (elt s (random (length s))))  => ?c ;e.g.  ; dash
(-as-> "abcd" s (aref s (random (length s)))) => ?c ;e.g.  ; dash
(let* ((s "abcd")
       (l (length s))
       (r (random l)))
  (aref s r))
=> ?c  ;e.g. ; C src

seq-find

(seq-find #'numberp '(a b 3 4 f 6)) => 3  ; seq
(-find #'numberp '(a b 3 4 f 6))    => 3  ; dash
(seq-find (lambda (elt) (not (numberp elt))) '(3 4 f 6)) => 'f  ; seq
(-find (lambda (elt) (not (numberp elt))) '(3 4 f 6))    => 'f  ; dash
(seq-find (-cut -> <> numberp not) '(3 4 f 6))           => 'f  ; seq  + dash
(-find (-cut -> <> numberp not) '(3 4 f 6))              => 'f  ; dash
(seq-find (##not (numberp %)) '(3 4 f 6))                => 'f  ; seq  + llama
(seq-find (-not #'numberp) '(3 4 f 6))                   => 'f  ; seq  + dash
(-find (##not (numberp %)) '(3 4 f 6))                   => 'f  ; dash + llama
(--find (not (numberp it)) '(3 4 f 6))                   => 'f  ; dash
(-find (-not #'numberp) '(3 4 f 6))                      => 'f  ; dash

seq-position

This function returns the position of the first match (in case there's more than one match).

;;; list
(seq-position '(a b c d c) 'c) => 2  ; seq
(-elem-index 'c '(a b c d c))  => 2  ; dash
;;; string
(seq-position "foobar" ?o) => 1  ; seq
(s-index-of "o" "foobar")  => 1  ; s

seq-positions

;;; list
(seq-positions '(a b c a d) 'a) => '(0 3)  ; seq
(-elem-indices 'a '(a b c a d)) => '(0 3)  ; dash
(seq-positions '(a b c a d) 'z) => nil  ; seq
(-elem-indices 'z '(a b c a d)) => nil  ; dash
(seq-positions '(11 5 7 12 9 15) 10 #'>=)                => '(0 3 5) ; seq
(-find-indices (lambda (x) (>= x 10)) '(11 5 7 12 9 15)) => '(0 3 5) ; dash
(-find-indices (-cut >= <> 10) '(11 5 7 12 9 15))        => '(0 3 5) ; dash
(-find-indices (##>= % 10) '(11 5 7 12 9 15))            => '(0 3 5) ; dash + llama
(--find-indices (>= it 10) '(11 5 7 12 9 15))            => '(0 3 5) ; dash

Unfortunately, s.el doesn't have an s-indices-of function (yet?).
So for strings it gets a bit more complicated:

;;; string
(seq-positions "foobar" ?o) => '(1 2)  ; seq
(s-with "foobar"                       ; s
  (s-matched-positions-all "o")
  (mapcar #'car))
=> '(1 2)

seq-length

;;; list
(seq-length '(a b c)) => 3  ; seq
(length '(a b c))     => 3  ; C src
;;; vector
(seq-length [a b c]) => 3  ; seq
(length [a b c])     => 3  ; C src
;;; string
(seq-length "abc") => 3  ; seq
(length "abc")     => 3  ; C src

seq-max

;;; list
(seq-max '(1 3 2)) => 3  ; seq
(-max '(1 3 2))    => 3  ; dash
;;; vector
(seq-max [1 3 2])          => 3  ; seq
(-max (append [1 3 2] ())) => 3  ; dash
(-max (--map it [1 3 2]))  => 3  ; dash

seq-min

;;; list
(seq-min '(1 3 2)) => 1  ; seq
(-min '(1 3 2))    => 1  ; dash
;;; vector
(seq-min [1 3 2])          => 1  ; seq
(-min (append [1 3 2] ())) => 1  ; dash
(-min (--map it [1 3 2]))  => 1  ; dash

seq-first

;;; list
(seq-first '(a b c)) => a  ; seq
(nth 0 '(a b c))     => a  ; C src
(elt '(a b c) 0)     => a  ; C src
(car '(a b c))       => a  ; C src
;;; vector
(seq-first [a b c]) => a  ; seq
(aref [a b c] 0)    => a  ; C src
(elt [a b c] 0)     => a  ; C src
;;; string → char
(seq-first "abc") => ?a  ; seq
(aref "abc" 0)    => ?a  ; C src
(elt "abc" 0)     => ?a  ; C src
;;; string → string
(string (seq-first "abc")) => "a"  ; C src + seq
(string (aref "abc" 0))    => "a"  ; C src + C src
(string (elt "abc" 0))     => "a"  ; C src + C src
(substring "abc" 0 1)      => "a"  ; C src
(s-left 1 "abc")           => "a"  ; s

seq-rest

;;; list
(seq-rest '(a b c)) => '(b c)  ; seq
(cdr '(a b c))      => '(b c)  ; C src
;;; string
(seq-rest "abc")    => "bc"  ; seq
(substring "abc" 1) => "bc"  ; C src
(s-right 2 "abc")   => "bc"  ; s
;;; vector
(seq-rest [a b c])    => [b c]  ; seq
(substring [a b c] 1) => [b c]  ; C src
(s-right 2 [a b c])   => [b c]  ; s

seq-reverse

;;; list
(seq-reverse '(1 4 2)) => '(2 4 1)  ; seq
(nreverse '(1 4 2))    => '(2 4 1)  ; C src (destructive!)
(reverse '(1 4 2))     => '(2 4 1)  ; C src
;;; vector
(seq-reverse [1 4 2]) => [2 4 1]  ; seq
(nreverse [1 4 2])    => [2 4 1]  ; C src (destructive!)
(reverse [1 4 2])     => [2 4 1]  ; C src
;;; string
(seq-reverse "142") => "241"  ; seq
(nreverse "142")    => "241"  ; C src (destructive!)
(reverse "142")     => "241"  ; C src

seq-sort

;;; list
(seq-sort #'> '(1 4 2)) => '(4 2 1)  ; seq
(-sort #'> '(1 4 2))    => '(4 2 1)  ; dash
(sort '(1 4 2) #'>)     => '(4 2 1)  ; C src (destructive!)
;;; vector
(seq-sort #'> [1 4 2]) => [4 2 1]  ; seq
(-sort #'> [1 4 2])    => [4 2 1]  ; dash
(sort [1 4 2] #'>)     => [4 2 1]  ; C src (destructive!)
;;; string
(seq-sort #'> "142") => "421" ; seq
(->> "142"      vconcat       ; dash
     (-sort #'>) concat)
=> "421"

seq-sort-by

;;; list
(seq-sort-by (lambda (a) (/ 1.0 a)) #'< '(1 2 3)) => '(3 2 1)  ; seq
(-sort (-on #'< (lambda (a) (/ 1.0 a))) '(1 2 3)) => '(3 2 1)  ; dash
(--sort (< (/ 1.0 it) (/ 1.0 other)) '(1 2 3))    => '(3 2 1)  ; dash
(seq-sort-by (-cut / 1.0 <>) #'< '(1 2 3))        => '(3 2 1)  ; seq  + dash
(-sort (-on #'< (-cut / 1.0 <>)) '(1 2 3))        => '(3 2 1)  ; dash
(-sort (-on #'< (##/ 1.0 %)) '(1 2 3))            => '(3 2 1)  ; dash + llama
(seq-sort-by (##/ 1.0 %) #'< '(1 2 3))            => '(3 2 1)  ; seq  + llama
;;; vector
(seq-sort-by #'seq-length #'> ["a" "ab" "abc"]) => ["abc" "ab" "a"]  ; seq
(-sort (-on #'> #'seq-length) ["a" "ab" "abc"]) => ["abc" "ab" "a"]  ; dash

Mapping Over Sequences

seq-map

(seq-map #'1+ '(1 2 3)) => '(2 3 4)  ; seq
(mapcar #'1+ '(1 2 3))  => '(2 3 4)  ; C src
(-map #'1+ '(1 2 3))    => '(2 3 4)  ; dash
(seq-map (lambda (n) (+ 40 n)) '(1 2 3)) => '(41 42 43)  ; seq
(mapcar (lambda (n) (+ 40 n)) '(1 2 3))  => '(41 42 43)  ; C src
(-map (lambda (n) (+ 40 n)) '(1 2 3))    => '(41 42 43)  ; dash
(seq-map (-cut + 40 <>) '(1 2 3))        => '(41 42 43)  ; seq   + dash
(mapcar (-cut + 40 <>) '(1 2 3))         => '(41 42 43)  ; C src + dash
(-map (-cut + 40 <>) '(1 2 3))           => '(41 42 43)  ; dash
(seq-map (##+ 40 %) '(1 2 3))            => '(41 42 43)  ; seq   + llama
(mapcar (##+ 40 %) '(1 2 3))             => '(41 42 43)  ; C src + llama
(-map (##+ 40 %) '(1 2 3))               => '(41 42 43)  ; dash  + llama
(--map (+ 40 it) '(1 2 3))               => '(41 42 43)  ; dash

seq-map-indexed

Careful here: seq-map-indexed and -map-indexed have their arguments in opposite order.

Therefore, we can only use -cut once in this example. (Can you see why?)

(seq-map-indexed (lambda (e i) (cons i e)) '(a b)) => '((0 . a) (1 . b))  ; seq
(-map-indexed (lambda (i e) (cons i e)) '(a b))    => '((0 . a) (1 . b))  ; dash
(--map-indexed (cons it-index it) '(a b))          => '((0 . a) (1 . b))  ; dash
(-map-indexed (-cut cons <> <>) '(a b))            => '((0 . a) (1 . b))  ; dash
(seq-map-indexed (##cons %2 %) '(a b))             => '((0 . a) (1 . b))  ; seq  + llama
(-map-indexed (##cons % %2) '(a b))                => '((0 . a) (1 . b))  ; dash + llama

I'd have preferred dash to have named the first anaphora of these indexed macros idx, or at least just index.

seq-mapcat

;;; string
(seq-mapcat #'upcase '("a" "b" "c") 'string) => "ABC"  ; seq
(mapconcat #'upcase '("a" "b" "c"))          => "ABC"  ; C src
;;; vector
(seq-mapcat #'upcase '("a" "b" "c") 'vector)  => [65 66 67]  ; seq
(vconcat (mapconcat #'upcase '("a" "b" "c"))) => [65 66 67]  ; C src + C src
;;; list
(seq-mapcat #'seq-reverse '((3 2 1) (6 5 4))) => '(1 2 3 4 5 6)  ; seq
(-mapcat #'seq-reverse '((3 2 1) (6 5 4)))    => '(1 2 3 4 5 6)  ; dash  + seq
(mapcan #'seq-reverse '((3 2 1) (6 5 4)))     => '(1 2 3 4 5 6)  ; C src + seq
(seq-mapcat #'reverse '((3 2 1) (6 5 4)))     => '(1 2 3 4 5 6)  ; seq   + C src
(-mapcat #'reverse '((3 2 1) (6 5 4)))        => '(1 2 3 4 5 6)  ; dash  + C src
(mapcan #'reverse '((3 2 1) (6 5 4)))         => '(1 2 3 4 5 6)  ; C src

seq-mapn

(seq-mapn #'+ '(2 4 6) '(20 40 60))  => '(22 44 66)  ; seq
(-zip-with #'+ '(2 4 6) '(20 40 60)) => '(22 44 66)  ; dash

seq-do

(seq-do #'princ '("foo" "bar"))       => '("foo" "bar")  ;⊣ foobar  ; seq
(mapc #'princ '("foo" "bar"))         => '("foo" "bar")  ;⊣ foobar  ; C src

;; but also:
(dolist (i '("foo" "bar")) (princ i)) => nil             ;⊣ foobar  ; subr
(--each '("foo" "bar") (princ it))    => nil             ;⊣ foobar  ; dash
(-each '("foo" "bar") #'princ)        => nil             ;⊣ foobar  ; dash

seq-do-indexed

These print the messages:

0:foo
1:bar

Careful here: seq-do-indexed and -each-indexed have their arguments in opposite order.

Therefore, we can only use -cut once in this example. (Can you see why?)

(seq-do-indexed (lambda (e i) (message "%s:%s" i e)) '("foo" "bar")) => nil  ; seq
(-each-indexed '("foo" "bar") (lambda (i e) (message "%s:%s" i e)))  => nil  ; dash
(-each-indexed '("foo" "bar") (-cut message "%s:%s" <> <>))          => nil  ; dash
(seq-do-indexed (##message "%s:%s" %2 %) '("foo" "bar"))             => nil  ; seq  + llama
(-each-indexed '("foo" "bar") (##message "%s:%s" % %2))              => nil  ; dash + llama
(--each '("foo" "bar") (message "%s:%s" it-index it))                => nil  ; dash

seq-reduce

;;; list
(seq-reduce #'+ '(1 2 3) 0)   => 6  ; seq
(-reduce-from #'+ 0 '(1 2 3)) => 6  ; dash
(-reduce #'+ '(1 2 3))        => 6  ; dash
;;; vector
(seq-reduce #'+ [1 2 3] 0)               => 6  ; seq
(-reduce-from #'+ 0 (append [1 2 3] ())) => 6  ; dash
(-reduce-from #'+ 0 (--map it [1 2 3]))  => 6  ; dash
(-reduce #'+ (append [1 2 3] ()))        => 6  ; dash
(-reduce #'+ (--map it [1 2 3]))         => 6  ; dash
;;; vector
(seq-reduce #'* [1 2 3] 2)               => 12  ; seq
(-reduce-from #'* 2 (append [1 2 3] ())) => 12  ; dash
(-reduce-from #'* 2 (--map it [1 2 3]))  => 12  ; dash

Excerpting Sequences

seq-drop

(seq-drop '(a b c) 2) => '(c)  ; seq
(nthcdr 2 '(a b c))   => '(c)  ; C src
(-drop 2 '(a b c))    => '(c)  ; dash

seq-drop-while

(seq-drop-while #'numberp '(1 2 c d 5)) => '(c d 5)  ; seq
(-drop-while #'numberp '(1 2 c d 5))    => '(c d 5)  ; dash

seq-filter

(seq-filter #'numberp '(a b 3 4 f 6)) => '(3 4 6)  ; seq
(-filter #'numberp '(a b 3 4 f 6))    => '(3 4 6)  ; dash
(-select #'numberp '(a b 3 4 f 6))    => '(3 4 6)  ; dash

seq-keep

(seq-keep #'car-safe '((1 2) 3 t (a . b))) => '(1 a)  ; seq
(-keep #'car-safe '((1 2) 3 t (a . b)))    => '(1 a)  ; dash

seq-remove

;;; list
(seq-remove #'numberp '(1 2 c d 5)) => '(c d)  ; seq
(-remove #'numberp '(1 2 c d 5))    => '(c d)  ; dash
(-reject #'numberp '(1 2 c d 5))    => '(c d)  ; dash
;;; list
(seq-remove (lambda (elt) (> elt 0)) '(1 -1 3 -3 5)) => '(-1 -3) ; seq
(-remove (lambda (elt) (> elt 0)) '(1 -1 3 -3 5))    => '(-1 -3) ; dash
(seq-remove (-cut > <> 0) '(1 -1 3 -3 5))            => '(-1 -3) ; seq  + dash
(-remove (-cut > <> 0) '(1 -1 3 -3 5))               => '(-1 -3) ; dash
(seq-remove (##> % 0) '(1 -1 3 -3 5))                => '(-1 -3) ; seq  + llama
(-remove (##> % 0) '(1 -1 3 -3 5))                   => '(-1 -3) ; dash + llama
(--remove (> it 0) '(1 -1 3 -3 5))                   => '(-1 -3) ; dash
;;; vector
(seq-remove (lambda (elt) (> elt 0)) [1 -1 3 -3 5])          => '(-1 -3) ; seq
(-remove (lambda (elt) (> elt 0)) (append [1 -1 3 -3 5] ())) => '(-1 -3) ; dash
(-remove (lambda (elt) (> elt 0)) (--map it [1 -1 3 -3 5]))  => '(-1 -3) ; dash
(-remove (-cut > <> 0) (append [1 -1 3 -3 5] ()))            => '(-1 -3) ; dash
(-remove (-cut > <> 0) (--map it [1 -1 3 -3 5]))             => '(-1 -3) ; dash
(-remove (##> % 0) (append [1 -1 3 -3 5] ()))                => '(-1 -3) ; dash + llama
(--remove (> it 0) (append [1 -1 3 -3 5] ()))                => '(-1 -3) ; dash
(-remove (##> % 0) (--map it [1 -1 3 -3 5]))                 => '(-1 -3) ; dash + llama
(--remove (> it 0) (--map it [1 -1 3 -3 5]))                 => '(-1 -3) ; dash
(seq-remove (-cut > <> 0) [1 -1 3 -3 5])                     => '(-1 -3) ; seq  + dash
(seq-remove (##> % 0) [1 -1 3 -3 5])                         => '(-1 -3) ; seq  + llama

seq-remove-at-position

;;; list
(seq-remove-at-position '(a b c d e) 3) => '(a b c e)  ; seq
(-remove-at 3 '(a b c d e))             => '(a b c e)  ; dash
;;; vector
(seq-remove-at-position [a b c d e] 0) => [b c d e]  ; seq
(-remove-at 0 (append [a b c d e] ())) => [b c d e]  ; dash
(-remove-at 0 (--map it [a b c d e]))  => [b c d e]  ; dash

seq-group-by

(seq-group-by #'natnump '(-1 2 3 -4 -5 6)) => '((nil -1 -4 -5) (t 2 3 6))  ; seq
(-group-by #'natnump '(-1 2 3 -4 -5 6))    => '((nil -1 -4 -5) (t 2 3 6))  ; dash

seq-union

(seq-union '(1 2 3) '(3 5)) => '(1 2 3 5)  ; seq
(-union '(1 2 3) '(3 5))    => '(1 2 3 5)  ; dash
(seq-union '(1 2 2 3) '(3 5)) => '(1 2 3 5)  ; seq
(-union '(1 2 2 3) '(3 5))    => '(1 2 3 5)  ; dash

Note that the behavior of seq-union seems inconsistent with that of seq-difference and seq-intersection:

(seq-union        '(1 2 2 3) '(3 5)) => '(1 2 3 5)  ; seq
(seq-difference   '(1 2 2 3) '(3 5)) => '(1 2 2)    ; seq
(seq-intersection '(1 2 2 3) '(2 5)) => '(2 2)      ; seq

That doesn't happen for dash versions >2.19.1:

(-union        '(1 2 2 3) '(3 5)) => '(1 2 3 5)     ; dash
(-difference   '(1 2 2 3) '(3 5)) => '(1 2)         ; dash
(-intersection '(1 2 2 3) '(2 5)) => '(2)           ; dash

seq-difference

;;; lists
(seq-difference '(1 2 3 2) '(2 3 4)) => '(1)  ; seq
(-difference '(1 2 3 2) '(2 3 4))    => '(1)  ; dash

But note that in dash versions after 2.19.1, dash returns proper sets for cases like this:

(seq-difference '(1 1 2 3 2) '(2 3 4)) => '(1 1)  ; seq
(-difference '(1 1 2 3 2) '(2 3 4))    => '(1)    ; dash
;;; list + vector
(seq-difference '(2 3 4 5) [1 3 5 6 7])          => '(2 4)  ; seq
(-difference '(2 3 4 5) (append [1 3 5 6 7] ())) => '(2 4)  ; dash
(-difference '(2 3 4 5) (--map it [1 3 5 6 7]))  => '(2 4)  ; dash

seq-intersection

(seq-intersection '(1 2 3) '(2 3 4)) => '(2 3)  ; seq
(-intersection '(1 2 3) '(2 3 4))    => '(2 3)  ; dash

But note that in dash versions after 2.19.1, dash returns proper sets for cases like this:

(seq-intersection '(1 2 1 3 2) '(2 3 4)) => '(2 3 2)  ; seq
(-intersection '(1 2 1 3 2) '(2 3 4))    => '(2 3)    ; dash

seq-subseq

;;; list
(seq-subseq '(a b c d e) 2 4) => '(c d)  ; seq
(-slice '(a b c d e) 2 4)     => '(c d)  ; dash
;;; string
(seq-subseq "emacslisp" 1 3) => "ma"  ; seq
(substring "emacslisp" 1 3)  => "ma"  ; C src
;;; vector
(seq-subseq [1 2 3 4 5] 1 3)                      => [2 3]  ; seq
(-> [1 2 3 4 5] (append ()) (-slice 1 3) vconcat) => [2 3]  ; dash + C src
;;; vector
(seq-subseq [1 2 3 4 5] 1)                      => [2 3 4 5]  ; seq
(-> [1 2 3 4 5] (append ()) (-slice 1) vconcat) => [2 3 4 5]  ; dash + C src

seq-take

;;; list
(seq-take '(a b c d e) 3) => '(a b c)  ; seq
(-take 3 '(a b c d e))    => '(a b c)  ; dash
;; Since Emacs 29.1:
(ntake 3 '(a b c d e))    => '(a b c)  ; C src (destructive!)
(take 3 '(a b c d e))     => '(a b c)  ; C src
;;; vector
(seq-take [a b c d e] 3)                       => [a b c]  ; seq
(->> [a b c d e] (--map it) (-take 3) vconcat) => [a b c]  ; dash + C src

seq-partition

(seq-partition '(a b c d e f g h) 3)  => '((a b c) (d e f) (g h))  ; seq
(-partition-all 3 '(a b c d e f g h)) => '((a b c) (d e f) (g h))  ; dash

Although they're coded separately and differently, seq-partition and seq-split seem to behave identically. Look:

(seq-split     [0 1 2 3] 2) => '([0 1] [2 3])
(seq-partition [0 1 2 3] 2) => '([0 1] [2 3])
(seq-split     [0 1 2 3 4] 2) => '([0 1] [2 3] [4])
(seq-partition [0 1 2 3 4] 2) => '([0 1] [2 3] [4])

The only difference seems to be when a non-positive integer is used:

(seq-split     [0 1 2 3 4] -2) !!> error  ; "Sub-sequence length must be larger than zero"
(seq-partition [0 1 2 3 4] -2)  => nil

for which dash, like seq-split, also throws an error...

(-partition-all -2 '(0 1 2 3 5)) !!> wrong-type-argument

...which comes with this nice message:

-partition-all-in-steps: Wrong type argument: "Step size < 1 results in juicy infinite loops", -2

seq-split

;;; list
(seq-split '(0 1 2 3 5) 2)      => '((0 1) (2 3) (5))  ; seq
(-partition-all 2 '(0 1 2 3 5)) => '((0 1) (2 3) (5))  ; dash
;;; vector
(seq-split [0 1 2 3 5] 2) => '([0 1] [2 3] [5])  ; seq
(->> [0 1 2 3 5]                                 ; dash
     (--map it)          ; vector → list
     (-partition-all 2)  ; partition it
     (-map #'vconcat))   ; list-elements → vector-elements
=> '([0 1] [2 3] [5])
;;; string
(seq-split "velociraptor" 5) => '("veloc" "irapt" "or")  ; seq
(->> "velociraptor"                                      ; dash
     (--map it)         ; string → list
     (-partition-all 5) ; partition it
     (-map #'concat))   ; list-elements → string-elements
=> '("veloc" "irapt" "or")

Disappointingly, s.el doesn't seem to have a function to do the above. There's s-split, but it does something else.

I propose it, then. It could be called s-partition.

Meanwhile, here are a couple of longer, much weirder solutions that use other s.el functions:

;;;; s + subr-x
(named-let part ((str "velociraptor") lst)
  (if (s-present? str)
      (part (s-chop-left 5 str) `(,@lst ,(s-left 5 str)))
    lst))
=> '("veloc" "irapt" "or")
;;;; s + subr + llama
(letrec ((part (##unless (s-blank? %2)
                 `(,(s-left % %2)
                   ,@(funcall part % (s-chop-left % %2))))))
  (funcall part 5 "velociraptor"))
=> '("veloc" "irapt" "or")

seq-take-while

;;; list
(seq-take-while #'integerp '(1 2 3.0 4)) => '(1 2)  ; seq
(-take-while #'integerp '(1 2 3.0 4))    => '(1 2)  ; dash
;;; vector
(seq-take-while #'integerp [1 2 3.0 4]) => [1 2]  ; seq
(->> [1 2 3.0 4]                                  ; dash
     (--map it)
     (-take-while #'integerp)
     vconcat)
=> [1 2]

seq-uniq

(seq-uniq '(a b d b a c)) => '(a b d c)  ; seq
(-uniq '(a b d b a c))    => '(a b d c)  ; dash

Binding

seq-let

(seq-let (a _ c) '(1 2 3 4) (list a c))          => '(1 3)  ; seq
(pcase-let ((`(,a _ ,c) '(1 2 3 4))) (list a c)) => '(1 3)  ; pcase
(-let [(a _ c) '(1 2 3 4)] (list a c))           => '(1 3)  ; dash

seq-setq

(let (a b) (seq-setq (a [_] b) '(1 [2 3] 4)) (cons a b))      => '(1 . 4)  ; seq
(let (a b) (pcase-setq `(,a [_] ,b) '(1 [2 3] 4)) (cons a b)) => '(1 . 4)  ; pcase
(let (a b) (-setq (a [_] b) '(1 [2 3] 4)) (cons a b))         => '(1 . 4)  ; dash

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)
(pcase-foo x)     ; pcase
(foo else)        ; C source code, subr, subr-x, simple, or cl-macs

Here are the details for that:

;;;; C source code (primitives), subr, subr-x, simple, or cl-macs
=> (list
    "cl-macs" '(cl-labels)
    "simple"  '(string-empty-p)
    "subr-x"  '(named-let)
    "subr"    '(dolist lambda letrec string= string-to-list when)
    "C src"   '(= and append apply aref car copy-sequence elt funcall length
                  let let* mapc mapcan mapcar mapconcat member nth nthcdr
                  reverse sequencep string string-equal substring take vconcat))

Either of the below will get you (roughly) the result above.

;;;;; dash solution
(let* ((funs '(= and append apply aref car copy-sequence cl-labels dolist elt
                 funcall lambda length let let* letrec mapc mapcan mapcar
                 mapconcat member named-let nth nthcdr reverse sequencep string
                 string= string-empty-p string-equal string-to-list substring
                 take vconcat when))
       (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 '(= and append apply aref car copy-sequence cl-labels dolist elt
                 funcall lambda length let let* letrec mapc mapcan mapcar
                 mapconcat member named-let nth nthcdr reverse sequencep string
                 string= string-empty-p string-equal string-to-list substring
                 take vconcat when))
       (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))

Excluded from 'funs are those used only as an argument, and which are therefore merely incidental to the construction of alternative solutions, such as the #'1+ here:

(seq-map #'1+ '(1 2 3)) => '(2 3 4)
(mapcar #'1+ '(1 2 3))  => '(2 3 4)
(-map #'1+ '(1 2 3))    => '(2 3 4)

Acknowledgments

;; I wrote the non-seq solutions, plus other seq usage examples
;; SPDX-FileCopyrightText:  © flandrew
;;
;; Most seq usage examples came from shortdoc.el
;; SPDX-FileCopyrightText:  © 2020-2024 Free Software Foundation, Inc.
;;
;; Some seq usage examples came from elisp-demos
;; SPDX-FileCopyrightText:  © 2018-2020 Xu Chunyang
;;
;; All of which 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 map.el.