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:
primitive
(C source code) (car
,nthcdr
,elt
,reverse
...)dash.el
seq.el
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
listleast, because I never seem to use it. In all my public Emacs packages, I see only oneseq
function — a single use of aseq-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.