1

add-to-list doesn't refresh item, it only checks for existence of item by equal or custom comparison function:

(add-to-list
 'tramp-methods
 '("gssh" (tramp-login-program "gcloud compute ssh"))
 nil (lambda (a b) (equal (car a) (car b))))

What way can I replace definition in a list, that handles presence/absence of item and support custom comparison function?

tramp-methods is an association list. Is there something to set key/value with replacing existing entry?

UPDATE I found cl-pushnew & cl-adjoin but they don't replace, only adds if not there...

UPDATE 2 Found exactly same question: https://stackoverflow.com/questions/10063195/replace-item-in-association-list-in-elisp

There is no build-in library function that handles replacement of existing items with custom key/test function & that handles missing item case...

The close solution:

(setq tramp-methods (cons
                     '("gssh" (tramp-login-program "compute ssh 2"))
                     (cl-remove "gssh" tramp-methods :key 'car :test 'equal)))

I wonder if there is some cl- equivalent...

UPDATE 3 cl-union is the most closed so far, but it has undefined behavior when elements are equal...

1
2

You can use alist-get with setf (elisp) Generalized Variables, e.g.,

;; Add
(let ((al (list (cons 'a 1) (cons 'b 2))))
  (setf (alist-get 'c al) 3)
  al)
;; => ((c . 3) (a . 1) (b . 2))

;; Replace/update
(let ((al (list (cons 'a 1) (cons 'b 2))))
  (setf (alist-get 'b al) "2")
  al)
;; => ((a . 1) (b . "2"))
(add-to-list
 'tramp-methods
 '("gssh" (tramp-login-program "gcloud compute ssh"))
 nil (lambda (a b) (equal (car a) (car b))))

(setf (alist-get "gssh" tramp-methods nil nil #'equal)
      '(tramp-login-program "GCLOUD COMPUTE SSH"))

(car tramp-methods)
;; => ("gssh" tramp-login-program "GCLOUD COMPUTE SSH")

alist-get was added in Emacs 25.1, its optional argument TESTFN was added in Emacs 26.1.

6
  • I believe your code doesn't handle the case when there is no key in the alist. Regarding setf - nice to know! Checks for key existence asks for some utility method/macro (to avoid duplication). – gavenkoa Nov 1 '20 at 14:45
  • 1
    If the key doesn't exist in the alist, then the setf will cause it to be added – rpluim Nov 1 '20 at 14:59
  • 1
    @gavenkoa in the first example, the key c does not exists, setf adds the key/value successfully. – xuchunyang Nov 1 '20 at 15:49
  • Yeah, it works. Now I need to grasp alist-get + setf. Seems that setf macro has special support for alist-get. – gavenkoa Nov 1 '20 at 17:39
  • 1
    @gavenkoa yeah, setf is interesing, e.g., (setf (point) (point-min)) expands into (goto-char (point-min)) – xuchunyang Nov 1 '20 at 17:53
1

I ended with function like:

(defun my-assoc-push (key value alist-name)
  (when (not (symbolp alist-name)) (error "alist-name is not a symbol."))
  (set alist-name
       (cons (cons key value)
             (cl-remove key (symbol-value alist-name) :key #'car :test #'equal))))

(my-assoc-push "gssh"  '((tramp-login-program "compute ssh 3")) 'tramp-methods)

to replace/add if new key to an association list.

1
  • 1
    I would rename the alist argument to alist-name in order to avoid the cognitive dissonance of "alist is not a symbol" - my reaction to that would be: "Of course, it's not a symbol - it's an alist" :-) – NickD Nov 1 '20 at 13:14
1

tramp-methods is an association list.

Which means that you don't have to replace an existing value at all.

When a value is looked up in an alist, only the first match for the key is returned.

Therefore merely pushing a new (KEY . VALUE) onto the front of the list has the desired effect, regardless of whether or not there are other uses of that same KEY in the list already.

2
  • During debugging I polluted alist with same keys. Will reloading of init files pollute alists too (with push, add-to-list tries to match based on equals at least...)? – gavenkoa Nov 1 '20 at 22:12
  • 1
    It all depends on what you want to do, really. add-to-list won't change anything if the specified key.value already exists in the list, but if it does exist but isn't the first instance of that key, then you also won't be getting that value back from a subsequent query. Conversely if you push then you'll definitely get the pushed value back from a subsequent query, but you might be pushing a redundant duplicate. – phils Nov 1 '20 at 22:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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