3

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)?

1
  • 4
    alist-get uses eq for testing by default, check its docstring.
    – xuchunyang
    Commented Nov 10, 2020 at 12:32

2 Answers 2

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))
3
  • 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
    Commented Nov 10, 2020 at 16:48
  • 3
    @gavenkoa C-h i m elisp RET i alist-get RET takes you to the documentation of alist-get in the Elisp manual, which in turn refers the reader to (elisp) Setting Generalized Variables, where the setf magic is described in more detail.
    – Basil
    Commented Nov 10, 2020 at 17:00
  • 2
    @Basil: +1. And C-h S alist-get takes you there directly.
    – Drew
    Commented Nov 10, 2020 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)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.