2

I got a suggestion to use setf to replace value in an alist: Replace element in a list / add in case of absence, with custom test/key functions

The example was provided, but it doesn't work if key is a string:

(let ((al (list (cons "a" 1) (cons "b" 2))))
  (setf (alist-get "c" al) 3)
  (setf (alist-get "c" al) 4)
  al)

;; evaluates to: (("c" . 4) ("c" . 3) ("a" . 1) ("b" . 2))

The keys of tramp-methods are strings. Is it possible to alter equality predicate of setf in a a declarative way (without tons of elisp)?

Drew
  • 75,699
  • 9
  • 109
  • 225
gavenkoa
  • 3,352
  • 19
  • 36

2 Answers2

3

Is it possible to alter equality predicate of setf in a a declarative way (without tons of elisp)?

See the docstring of alist-get:

alist-get is a compiled Lisp function in ‘subr.el’.

(alist-get KEY ALIST &optional DEFAULT REMOVE TESTFN)

  Probably introduced at or before Emacs version 25.1.

Find the first element of ALIST whose ‘car’ equals KEY and return its ‘cdr’.
If KEY is not found in ALIST, return DEFAULT.
Equality with KEY is tested by TESTFN, defaulting to ‘eq’.

You can use ‘alist-get’ in PLACE expressions.  This will modify
an existing association (more precisely, the first one if
multiple exist), or add a new element to the beginning of ALIST,
destructively modifying the list stored in ALIST.

Example:

   (setq foo '((a . 0)))
   (setf (alist-get 'a foo) 1
         (alist-get 'b foo) 2)

   foo => ((b . 2) (a . 1))


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 (more precisely the first found association will be
deleted from the alist).

Example:

  (setq foo '((a . 1) (b . 2)))
  (setf (alist-get 'b foo nil 'remove) nil)

  foo => ((a . 1))

The key point to note is the TESTFN argument, which defaults to eq but can also be set to equal:

(let (alist)
  (setf (alist-get "a" alist nil nil #'equal) 1)
  (setf (alist-get "a" alist nil nil #'equal) 2)
  alist) ; => (("a" . 2))
Basil
  • 12,019
  • 43
  • 69
  • Documentation looked as garbage for me until I started to appreciate writing. I thought that `setf` magic is explained somewhere else, not in `alist-get` itself... Seems we should expect spreading of `setf` usage in a future )) – gavenkoa Nov 10 '20 at 16:48
  • 3
    @gavenkoa `C-h i m elisp RET i alist-get RET` takes you to [the documentation of `alist-get`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Association-Lists.html) in the Elisp manual, which in turn refers the reader to [`(elisp) Setting Generalized Variables`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Generalized-Variables.html#Generalized-Variables), where the `setf` magic is described in more detail. – Basil Nov 10 '20 at 17:00
  • 2
    @Basil: +1. And `C-h S alist-get` takes you there directly. – Drew Nov 10 '20 at 18:41
2

After reading of emacs/27.1/lisp/emacs-lisp/gv.el:

(gv-define-expander alist-get
  (lambda (do key alist &optional default remove testfn)

I came up with:

(let ((al (list (cons "a" 1) (cons "b" 2))))
  (setf (alist-get "c" al nil t #'equal) 3)
  (setf (alist-get "c" al nil t #'equal) 4)
  al)

;; produces: (("c" . 4) ("a" . 1) ("b" . 2))

It correlates with a alist-get signature:

(alist-get KEY ALIST &optional DEFAULT REMOVE TESTFN)
gavenkoa
  • 3,352
  • 19
  • 36