aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTino Calancha2017-07-17 21:30:50 +0900
committerTino Calancha2017-07-17 21:30:50 +0900
commit76e1f7d00fbff7bf8183ba85db2f67a11aa2d5ce (patch)
treeac3d9fbe5fa46dbad70b527355e2f1ba997f36f8
parent4968aa685b85840d79258ff6b61ba2bcfb99e2bc (diff)
downloademacs-76e1f7d00fbff7bf8183ba85db2f67a11aa2d5ce.tar.gz
emacs-76e1f7d00fbff7bf8183ba85db2f67a11aa2d5ce.zip
alist-get: Add optional arg TESTFN
If TESTFN is non-nil, then it is the predicate to lookup the alist. Otherwise, use 'eq' (Bug#27584). * lisp/subr.el (alist-get): Add optional arg FULL. * lisp/emacs-lisp/map.el (map-elt, map-put): Add optional arg TESTFN. * lisp/emacs-lisp/gv.el (alist-get): Update expander. * doc/lispref/lists.texi (Association Lists): Update manual. * etc/NEWS: Announce the changes. * test/lisp/emacs-lisp/map-tests.el (test-map-put-testfn-alist) (test-map-elt-testfn): New tests.
-rw-r--r--doc/lispref/lists.texi24
-rw-r--r--etc/NEWS3
-rw-r--r--lisp/emacs-lisp/gv.el6
-rw-r--r--lisp/emacs-lisp/map.el21
-rw-r--r--lisp/subr.el9
-rw-r--r--test/lisp/emacs-lisp/map-tests.el12
6 files changed, 52 insertions, 23 deletions
diff --git a/doc/lispref/lists.texi b/doc/lispref/lists.texi
index 966d8f18b17..0c993806824 100644
--- a/doc/lispref/lists.texi
+++ b/doc/lispref/lists.texi
@@ -1589,16 +1589,20 @@ keys may not be symbols:
1589@end smallexample 1589@end smallexample
1590@end defun 1590@end defun
1591 1591
1592@defun alist-get key alist &optional default remove 1592@defun alist-get key alist &optional default remove testfn
1593This function is like @code{assq}, but instead of returning the entire 1593This function is similar to @code{assq}. It finds the first
1594association for @var{key} in @var{alist}, 1594association @w{@code{(@var{key} . @var{value})}} by comparing
1595@w{@code{(@var{key} . @var{value})}}, it returns just the @var{value}. 1595@var{key} with @var{alist} elements, and, if found, returns the
1596If @var{key} is not found in @var{alist}, it returns @var{default}. 1596@var{value} of that association. If no association is found, the
1597 1597function returns @var{default}. Comparison of @var{key} against
1598This is a generalized variable (@pxref{Generalized Variables}) that 1598@var{alist} elements uses the function specified by @var{testfn},
1599can be used to change a value with @code{setf}. When using it to set 1599defaulting to @code{eq}.
1600a value, optional argument @var{remove} non-@code{nil} means to remove 1600
1601@var{key} from @var{alist} if the new value is @code{eql} to @var{default}. 1601This is a generalized variable (@pxref{Generalized Variables})
1602that can be used to change a value with @code{setf}. When
1603using it to set a value, optional argument @var{remove} non-@code{nil}
1604means to remove @var{key}'s association from @var{alist} if the new
1605value is @code{eql} to @var{default}.
1602@end defun 1606@end defun
1603 1607
1604@defun rassq value alist 1608@defun rassq value alist
diff --git a/etc/NEWS b/etc/NEWS
index edb71118efd..dca562cb3b9 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1119,6 +1119,9 @@ break.
1119 1119
1120* Lisp Changes in Emacs 26.1 1120* Lisp Changes in Emacs 26.1
1121 1121
1122+++
1123** New optional argument TESTFN in 'alist-get', 'map-elt' and 'map-put'.
1124
1122** New function 'seq-set-equal-p' to check if SEQUENCE1 and SEQUENCE2 1125** New function 'seq-set-equal-p' to check if SEQUENCE1 and SEQUENCE2
1123contain the same elements, regardless of the order. 1126contain the same elements, regardless of the order.
1124 1127
diff --git a/lisp/emacs-lisp/gv.el b/lisp/emacs-lisp/gv.el
index c5c12a6414c..27376fc7f95 100644
--- a/lisp/emacs-lisp/gv.el
+++ b/lisp/emacs-lisp/gv.el
@@ -377,10 +377,12 @@ The return value is the last VAL in the list.
377 `(with-current-buffer ,buf (set (make-local-variable ,var) ,v)))) 377 `(with-current-buffer ,buf (set (make-local-variable ,var) ,v))))
378 378
379(gv-define-expander alist-get 379(gv-define-expander alist-get
380 (lambda (do key alist &optional default remove) 380 (lambda (do key alist &optional default remove testfn)
381 (macroexp-let2 macroexp-copyable-p k key 381 (macroexp-let2 macroexp-copyable-p k key
382 (gv-letplace (getter setter) alist 382 (gv-letplace (getter setter) alist
383 (macroexp-let2 nil p `(assq ,k ,getter) 383 (macroexp-let2 nil p `(if (and ,testfn (not (eq ,testfn 'eq)))
384 (assoc ,k ,getter ,testfn)
385 (assq ,k ,getter))
384 (funcall do (if (null default) `(cdr ,p) 386 (funcall do (if (null default) `(cdr ,p)
385 `(if ,p (cdr ,p) ,default)) 387 `(if ,p (cdr ,p) ,default))
386 (lambda (v) 388 (lambda (v)
diff --git a/lisp/emacs-lisp/map.el b/lisp/emacs-lisp/map.el
index a89457e877d..31ba075c40f 100644
--- a/lisp/emacs-lisp/map.el
+++ b/lisp/emacs-lisp/map.el
@@ -4,7 +4,7 @@
4 4
5;; Author: Nicolas Petton <nicolas@petton.fr> 5;; Author: Nicolas Petton <nicolas@petton.fr>
6;; Keywords: convenience, map, hash-table, alist, array 6;; Keywords: convenience, map, hash-table, alist, array
7;; Version: 1.1 7;; Version: 1.2
8;; Package: map 8;; Package: map
9 9
10;; Maintainer: emacs-devel@gnu.org 10;; Maintainer: emacs-devel@gnu.org
@@ -93,11 +93,13 @@ Returns the result of evaluating the form associated with MAP-VAR's type."
93 ((arrayp ,map-var) ,(plist-get args :array)) 93 ((arrayp ,map-var) ,(plist-get args :array))
94 (t (error "Unsupported map: %s" ,map-var))))) 94 (t (error "Unsupported map: %s" ,map-var)))))
95 95
96(defun map-elt (map key &optional default) 96(defun map-elt (map key &optional default testfn)
97 "Lookup KEY in MAP and return its associated value. 97 "Lookup KEY in MAP and return its associated value.
98If KEY is not found, return DEFAULT which defaults to nil. 98If KEY is not found, return DEFAULT which defaults to nil.
99 99
100If MAP is a list, `eql' is used to lookup KEY. 100If MAP is a list, `eql' is used to lookup KEY. Optional argument
101TESTFN, if non-nil, means use its function definition instead of
102`eql'.
101 103
102MAP can be a list, hash-table or array." 104MAP can be a list, hash-table or array."
103 (declare 105 (declare
@@ -106,30 +108,33 @@ MAP can be a list, hash-table or array."
106 (gv-letplace (mgetter msetter) `(gv-delay-error ,map) 108 (gv-letplace (mgetter msetter) `(gv-delay-error ,map)
107 (macroexp-let2* nil 109 (macroexp-let2* nil
108 ;; Eval them once and for all in the right order. 110 ;; Eval them once and for all in the right order.
109 ((key key) (default default)) 111 ((key key) (default default) (testfn testfn))
110 `(if (listp ,mgetter) 112 `(if (listp ,mgetter)
111 ;; Special case the alist case, since it can't be handled by the 113 ;; Special case the alist case, since it can't be handled by the
112 ;; map--put function. 114 ;; map--put function.
113 ,(gv-get `(alist-get ,key (gv-synthetic-place 115 ,(gv-get `(alist-get ,key (gv-synthetic-place
114 ,mgetter ,msetter) 116 ,mgetter ,msetter)
115 ,default) 117 ,default nil ,testfn)
116 do) 118 do)
117 ,(funcall do `(map-elt ,mgetter ,key ,default) 119 ,(funcall do `(map-elt ,mgetter ,key ,default)
118 (lambda (v) `(map--put ,mgetter ,key ,v))))))))) 120 (lambda (v) `(map--put ,mgetter ,key ,v)))))))))
119 (map--dispatch map 121 (map--dispatch map
120 :list (alist-get key map default) 122 :list (alist-get key map default nil testfn)
121 :hash-table (gethash key map default) 123 :hash-table (gethash key map default)
122 :array (if (and (>= key 0) (< key (seq-length map))) 124 :array (if (and (>= key 0) (< key (seq-length map)))
123 (seq-elt map key) 125 (seq-elt map key)
124 default))) 126 default)))
125 127
126(defmacro map-put (map key value) 128(defmacro map-put (map key value &optional testfn)
127 "Associate KEY with VALUE in MAP and return VALUE. 129 "Associate KEY with VALUE in MAP and return VALUE.
128If KEY is already present in MAP, replace the associated value 130If KEY is already present in MAP, replace the associated value
129with VALUE. 131with VALUE.
132When MAP is a list, test equality with TESTFN if non-nil, otherwise use `eql'.
133TESTFN, if non-nil, means use its function definition instead of
134`eql'.
130 135
131MAP can be a list, hash-table or array." 136MAP can be a list, hash-table or array."
132 `(setf (map-elt ,map ,key) ,value)) 137 `(setf (map-elt ,map ,key nil ,testfn) ,value))
133 138
134(defun map-delete (map key) 139(defun map-delete (map key)
135 "Delete KEY from MAP and return MAP. 140 "Delete KEY from MAP and return MAP.
diff --git a/lisp/subr.el b/lisp/subr.el
index a9edff6166f..d9d918ed12d 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -725,15 +725,18 @@ Elements of ALIST that are not conses are ignored."
725 (setq tail tail-cdr)))) 725 (setq tail tail-cdr))))
726 alist) 726 alist)
727 727
728(defun alist-get (key alist &optional default remove) 728(defun alist-get (key alist &optional default remove testfn)
729 "Return the value associated with KEY in ALIST, using `assq'. 729 "Return the value associated with KEY in ALIST.
730If KEY is not found in ALIST, return DEFAULT. 730If KEY is not found in ALIST, return DEFAULT.
731Use TESTFN to lookup in the alist if non-nil. Otherwise, use `assq'.
731 732
732This is a generalized variable suitable for use with `setf'. 733This is a generalized variable suitable for use with `setf'.
733When using it to set a value, optional argument REMOVE non-nil 734When using it to set a value, optional argument REMOVE non-nil
734means to remove KEY from ALIST if the new value is `eql' to DEFAULT." 735means to remove KEY from ALIST if the new value is `eql' to DEFAULT."
735 (ignore remove) ;;Silence byte-compiler. 736 (ignore remove) ;;Silence byte-compiler.
736 (let ((x (assq key alist))) 737 (let ((x (if (not testfn)
738 (assq key alist)
739 (assoc key alist testfn))))
737 (if x (cdr x) default))) 740 (if x (cdr x) default)))
738 741
739(defun remove (elt seq) 742(defun remove (elt seq)
diff --git a/test/lisp/emacs-lisp/map-tests.el b/test/lisp/emacs-lisp/map-tests.el
index 07e85cc5391..15b0655040c 100644
--- a/test/lisp/emacs-lisp/map-tests.el
+++ b/test/lisp/emacs-lisp/map-tests.el
@@ -63,6 +63,11 @@ Evaluate BODY for each created map.
63 (with-maps-do map 63 (with-maps-do map
64 (should (= 5 (map-elt map 7 5))))) 64 (should (= 5 (map-elt map 7 5)))))
65 65
66(ert-deftest test-map-elt-testfn ()
67 (let ((map (list (cons "a" 1) (cons "b" 2))))
68 (should-not (map-elt map "a"))
69 (should (map-elt map "a" nil 'equal))))
70
66(ert-deftest test-map-elt-with-nil-value () 71(ert-deftest test-map-elt-with-nil-value ()
67 (should (null (map-elt '((a . 1) 72 (should (null (map-elt '((a . 1)
68 (b)) 73 (b))
@@ -94,6 +99,13 @@ Evaluate BODY for each created map.
94 (should (eq (map-elt alist 2) 99 (should (eq (map-elt alist 2)
95 'b)))) 100 'b))))
96 101
102(ert-deftest test-map-put-testfn-alist ()
103 (let ((alist (list (cons "a" 1) (cons "b" 2))))
104 (map-put alist "a" 3 'equal)
105 (should-not (cddr alist))
106 (map-put alist "a" 9)
107 (should (cddr alist))))
108
97(ert-deftest test-map-put-return-value () 109(ert-deftest test-map-put-return-value ()
98 (let ((ht (make-hash-table))) 110 (let ((ht (make-hash-table)))
99 (should (eq (map-put ht 'a 'hello) 'hello)))) 111 (should (eq (map-put ht 'a 'hello) 'hello))))