all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Tino Calancha <tino.calancha@gmail.com>
To: Nicolas Petton <nicolas@petton.fr>
Cc: 27584@debbugs.gnu.org, monnier@iro.umontreal.ca
Subject: bug#27584: 26.0.50; alist-get: Add optional arg TESTFN
Date: Tue, 11 Jul 2017 18:19:56 +0900	[thread overview]
Message-ID: <871spnwcsj.fsf@calancha-pc> (raw)
In-Reply-To: <8737a3qttj.fsf@strawberry> (Nicolas Petton's message of "Tue, 11 Jul 2017 10:08:40 +0200")

Nicolas Petton <nicolas@petton.fr> writes:

> Tino, would you like to adapt your patch to use the new assoc?
Here we are:

--8<-----------------------------cut here---------------start------------->8---
commit 2a9fb44ddae0acbd09c3123f06981d291163e765
Author: Tino Calancha <tino.calancha@gmail.com>
Date:   Tue Jul 11 18:17:00 2017 +0900

    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.

diff --git a/doc/lispref/lists.texi b/doc/lispref/lists.texi
index 966d8f18b1..39353b6de6 100644
--- a/doc/lispref/lists.texi
+++ b/doc/lispref/lists.texi
@@ -1589,16 +1589,20 @@ Association Lists
 @end smallexample
 @end defun
 
-@defun alist-get key alist &optional default remove
-This function is like @code{assq}, but instead of returning the entire
-association for @var{key} in @var{alist},
-@w{@code{(@var{key} . @var{value})}}, it returns just the @var{value}.
-If @var{key} is not found in @var{alist}, it returns @var{default}.
-
-This is a generalized variable (@pxref{Generalized Variables}) that
-can be used to change a value with @code{setf}.  When using it to set
-a value, optional argument @var{remove} non-@code{nil} means to remove
-@var{key} from @var{alist} if the new value is @code{eql} to @var{default}.
+@defun alist-get key alist &optional default remove testfn
+This function is similar to @code{assq}.  It finds the first
+association @w{@code{(@var{key} . @var{value})}} by comparing
+@var{key} with @var{alist} elements, and, if found, returns the
+@var{value} of that association.  If no association is found, the
+function returns @var{default}.  Comparison of @var{key} against
+@var{alist} elements uses the function specified by @var{testfn},
+defaulting to @code{eq}.
+
+The return value is a generalized variable (@pxref{Generalized
+Variables}) that can be used to change a value with @code{setf}.  When
+using it to set a value, optional argument @var{remove} non-@code{nil}
+means to remove @var{key}'s association from @var{alist} if the new
+value is @code{eql} to @var{default}.
 @end defun
 
 @defun rassq value alist
diff --git a/etc/NEWS b/etc/NEWS
index 68ebdb3c15..eb61e7d182 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1105,6 +1105,9 @@ break.
 \f
 * Lisp Changes in Emacs 26.1
 
++++
+** New optional argument TESTFN in 'alist-get', 'map-elt' and 'map-put'.
+
 ** New function 'seq-set-equal-p' to check if SEQUENCE1 and SEQUENCE2
 contain the same elements, regardless of the order.
 
diff --git a/lisp/emacs-lisp/gv.el b/lisp/emacs-lisp/gv.el
index c5c12a6414..27376fc7f9 100644
--- a/lisp/emacs-lisp/gv.el
+++ b/lisp/emacs-lisp/gv.el
@@ -377,10 +377,12 @@ setf
     `(with-current-buffer ,buf (set (make-local-variable ,var) ,v))))
 
 (gv-define-expander alist-get
-  (lambda (do key alist &optional default remove)
+  (lambda (do key alist &optional default remove testfn)
     (macroexp-let2 macroexp-copyable-p k key
       (gv-letplace (getter setter) alist
-        (macroexp-let2 nil p `(assq ,k ,getter)
+        (macroexp-let2 nil p `(if (and ,testfn (not (eq ,testfn 'eq)))
+                                  (assoc ,k ,getter ,testfn)
+                                (assq ,k ,getter))
           (funcall do (if (null default) `(cdr ,p)
                         `(if ,p (cdr ,p) ,default))
                    (lambda (v)
diff --git a/lisp/emacs-lisp/map.el b/lisp/emacs-lisp/map.el
index a89457e877..31ba075c40 100644
--- a/lisp/emacs-lisp/map.el
+++ b/lisp/emacs-lisp/map.el
@@ -4,7 +4,7 @@
 
 ;; Author: Nicolas Petton <nicolas@petton.fr>
 ;; Keywords: convenience, map, hash-table, alist, array
-;; Version: 1.1
+;; Version: 1.2
 ;; Package: map
 
 ;; Maintainer: emacs-devel@gnu.org
@@ -93,11 +93,13 @@ map-let
            ((arrayp ,map-var) ,(plist-get args :array))
            (t (error "Unsupported map: %s" ,map-var)))))
 
-(defun map-elt (map key &optional default)
+(defun map-elt (map key &optional default testfn)
   "Lookup KEY in MAP and return its associated value.
 If KEY is not found, return DEFAULT which defaults to nil.
 
-If MAP is a list, `eql' is used to lookup KEY.
+If MAP is a list, `eql' is used to lookup KEY.  Optional argument
+TESTFN, if non-nil, means use its function definition instead of
+`eql'.
 
 MAP can be a list, hash-table or array."
   (declare
@@ -106,30 +108,33 @@ map-elt
       (gv-letplace (mgetter msetter) `(gv-delay-error ,map)
         (macroexp-let2* nil
             ;; Eval them once and for all in the right order.
-            ((key key) (default default))
+            ((key key) (default default) (testfn testfn))
           `(if (listp ,mgetter)
                ;; Special case the alist case, since it can't be handled by the
                ;; map--put function.
                ,(gv-get `(alist-get ,key (gv-synthetic-place
                                           ,mgetter ,msetter)
-                                    ,default)
+                                    ,default nil ,testfn)
                         do)
              ,(funcall do `(map-elt ,mgetter ,key ,default)
                        (lambda (v) `(map--put ,mgetter ,key ,v)))))))))
   (map--dispatch map
-    :list (alist-get key map default)
+    :list (alist-get key map default nil testfn)
     :hash-table (gethash key map default)
     :array (if (and (>= key 0) (< key (seq-length map)))
                (seq-elt map key)
              default)))
 
-(defmacro map-put (map key value)
+(defmacro map-put (map key value &optional testfn)
   "Associate KEY with VALUE in MAP and return VALUE.
 If KEY is already present in MAP, replace the associated value
 with VALUE.
+When MAP is a list, test equality with TESTFN if non-nil, otherwise use `eql'.
+TESTFN, if non-nil, means use its function definition instead of
+`eql'.
 
 MAP can be a list, hash-table or array."
-  `(setf (map-elt ,map ,key) ,value))
+  `(setf (map-elt ,map ,key nil ,testfn) ,value))
 
 (defun map-delete (map key)
   "Delete KEY from MAP and return MAP.
diff --git a/lisp/subr.el b/lisp/subr.el
index a9edff6166..d9d918ed12 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -725,15 +725,18 @@ rassq-delete-all
 	(setq tail tail-cdr))))
   alist)
 
-(defun alist-get (key alist &optional default remove)
-  "Return the value associated with KEY in ALIST, using `assq'.
+(defun alist-get (key alist &optional default remove testfn)
+  "Return the value associated with KEY in ALIST.
 If KEY is not found in ALIST, return DEFAULT.
+Use TESTFN to lookup in the alist if non-nil.  Otherwise, use `assq'.
 
 This is a generalized variable suitable for use with `setf'.
 When using it to set a value, optional argument REMOVE non-nil
 means to remove KEY from ALIST if the new value is `eql' to DEFAULT."
   (ignore remove) ;;Silence byte-compiler.
-  (let ((x (assq key alist)))
+  (let ((x (if (not testfn)
+               (assq key alist)
+             (assoc key alist testfn))))
     (if x (cdr x) default)))
 
 (defun remove (elt seq)
diff --git a/test/lisp/emacs-lisp/map-tests.el b/test/lisp/emacs-lisp/map-tests.el
index 07e85cc539..15b0655040 100644
--- a/test/lisp/emacs-lisp/map-tests.el
+++ b/test/lisp/emacs-lisp/map-tests.el
@@ -63,6 +63,11 @@ with-maps-do
   (with-maps-do map
     (should (= 5 (map-elt map 7 5)))))
 
+(ert-deftest test-map-elt-testfn ()
+  (let ((map (list (cons "a" 1) (cons "b" 2))))
+    (should-not (map-elt map "a"))
+    (should (map-elt map "a" nil 'equal))))
+
 (ert-deftest test-map-elt-with-nil-value ()
   (should (null (map-elt '((a . 1)
                            (b))
@@ -94,6 +99,13 @@ with-maps-do
     (should (eq (map-elt alist 2)
                 'b))))
 
+(ert-deftest test-map-put-testfn-alist ()
+  (let ((alist (list (cons "a" 1) (cons "b" 2))))
+    (map-put alist "a" 3 'equal)
+    (should-not (cddr alist))
+    (map-put alist "a" 9)
+    (should (cddr alist))))
+
 (ert-deftest test-map-put-return-value ()
   (let ((ht (make-hash-table)))
     (should (eq (map-put ht 'a 'hello) 'hello))))
--8<-----------------------------cut here---------------end--------------->8---
In GNU Emacs 26.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.22.11)
 of 2017-07-11
Repository revision: 0bece6c6815cc59e181817a2765a4ea752f34f56





  reply	other threads:[~2017-07-11  9:19 UTC|newest]

Thread overview: 48+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-07-05  3:22 bug#27584: 26.0.50; alist-get: Add optional arg TESTFN Tino Calancha
2017-07-05  8:53 ` Tino Calancha
2017-07-05  9:19 ` Nicolas Petton
2017-07-05 13:18   ` Tino Calancha
2017-07-06  6:05     ` Tino Calancha
2017-07-06  6:13       ` Stefan Monnier
2017-07-06  6:20         ` Tino Calancha
2017-07-06  9:36           ` Nicolas Petton
2017-07-06 10:55             ` Tino Calancha
2017-07-06 11:06               ` Nicolas Petton
2017-07-06 15:07           ` Stefan Monnier
2017-07-07  6:48             ` Tino Calancha
2017-07-07  7:46               ` Eli Zaretskii
2017-07-07  8:09               ` Nicolas Petton
2017-07-07 15:53               ` Stefan Monnier
2017-07-09 14:45                 ` Tino Calancha
2017-07-10 12:04                   ` Michael Heerdegen
2017-07-10 12:28                     ` Tino Calancha
2017-07-10 12:38                       ` Michael Heerdegen
2017-07-10 12:47                   ` Michael Heerdegen
2017-07-10 13:02                     ` Tino Calancha
2017-07-10 13:18                       ` Michael Heerdegen
2017-07-10 12:50               ` Michael Heerdegen
2017-07-06 14:56       ` Nicolas Petton
2017-07-07  6:39         ` Tino Calancha
2017-07-07  8:11           ` Nicolas Petton
2017-07-07  8:22             ` Tino Calancha
2017-07-07  8:34               ` Nicolas Petton
2017-07-07 15:49                 ` Stefan Monnier
2017-07-07 15:54                   ` Nicolas Petton
2017-07-07 19:47                   ` Nicolas Petton
2017-07-08  6:30                     ` Eli Zaretskii
2017-07-08  7:02                       ` Tino Calancha
2017-07-08  7:14                         ` Eli Zaretskii
2017-07-08 11:32                           ` Nicolas Petton
2017-07-08 11:46                             ` Eli Zaretskii
2017-07-09 14:48                               ` Tino Calancha
2017-07-09 19:18                               ` Nicolas Petton
2017-07-11  8:08                               ` Nicolas Petton
2017-07-11  9:19                                 ` Tino Calancha [this message]
2017-07-12 17:36                                   ` Michael Heerdegen
2017-07-14  5:19                                     ` Tino Calancha
2017-07-14 11:16                                       ` Nicolas Petton
2017-07-17 13:38                                         ` Tino Calancha
2017-08-01 16:37                                 ` Nicolas Petton
2017-08-01 16:49                                   ` Nicolas Petton
2017-08-01 18:53                                     ` Eli Zaretskii
2017-07-08 11:29                         ` Nicolas Petton

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=871spnwcsj.fsf@calancha-pc \
    --to=tino.calancha@gmail.com \
    --cc=27584@debbugs.gnu.org \
    --cc=monnier@iro.umontreal.ca \
    --cc=nicolas@petton.fr \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.