11

I have not found a standard Elisp library function to merge two property lists, like this:

(setq pl nil)
(setq pl (plist-put pl 'key-1 'value-1))
(setq pl (plist-put pl 'key-2 'value-2))

I could build something with dolist, but before I do, I would like to check that I'm not overlooking an existing function in some library.

Updates, based on the comments:

  1. In response to the "many-ways" comment:

I would imagine that there isn't such a function because there are different (and possibly valid) answers possible to the question: what to do when you have duplicate property names with distinct values?

Yes, there is a question on how to merge duplicates, but there are relatively few ways to address that. I see two general approaches. First, argument order could resolve the duplicates; e.g. rightmost wins, as in Clojure's merge. Second, the merge could delegate to a user-provided callback function, as in Ruby's merge.

In any case, the fact that there are different ways of doing it does not prevent many other language standard libraries from providing a merge function. The same general argument could be said of sorting, and yet Elisp provides sorting functionality.

  1. "Could you elaborate?" / "Please specify the behavior you are looking for precisely."

Generally speaking, I'm open to what the Elisp community uses. If you would like a specific example, here would be one example that would work:

(a-merge-function '(k1 1) '(k2 2 k3 3) '(k3 0))

And returns

'(k1 1 k2 2 k3 0))

This would be a rightmost-wins style, like Clojure's merge.

  1. "They're lists, so just append?"

No, append does not preserve property list semantics. This:

(append '(k1 1 k2 2) '(k2 0))

Returns this:

(k1 1 k2 2 k2 0)

append is a built-in function in `C source code'.

(append &rest SEQUENCES)

Concatenate all the arguments and make the result a list. The result is a list whose elements are the elements of all the arguments. Each argument may be a list, vector or string. The last argument is not copied, just used as the tail of the new list.

  1. "And your example does not show anything like a merge - it does not even show two property lists."

Yes it does; it does the merge step by step. It shows how doing a merge using the Elisp's documented property list functions is painfully verbose:

(setq pl nil)
(setq pl (plist-put pl 'key-1 'value-1))
(setq pl (plist-put pl 'key-2 'value-2))

Just display the resulting output value from pl:

(key-1 value-1 key-2 value-2)

To reiterate, I am capable of writing a function to solve this problem, but I first wanted to figure out if such a function exists somewhere in common use.

Finally, if you downvoted the question because you found it unclear, I would ask that you reconsider now that I've gone to some effort to clarify. This is not a lack of research. The Elisp documentation on "Lists" does not answer the question.

  • 2
    They're lists, so just append? – abo-abo Dec 16 '14 at 9:18
  • 2
    Please specify the behavior you are looking for precisely. There are many ways to "merge" two lists. And your example does not show anything like a merge - it does not even show two property lists. So far, this question shouldbe closed as unclear. FWIW, be aware that a pair closer to the front of a plist shadows any pair having the same key that is farther from the front. So merging can mean putting elements from one plist before elements from the other, etc. – Drew Dec 16 '14 at 16:50
  • 1
    @abo-abo: It turns out that the Emacs Lisp Manual explicitly states that property names must be distinct. – Constantine Dec 16 '14 at 18:45
  • 3
    To make the rightmost list win you just have to reverse the order of the lists you pass to append: (let ((args '((:a 1 :b 1) (:b 2) (:a 3)))) (apply #'append (reverse args))) => (:a 3 :b 2 :a 1 :b 1) which is the same as (:a 3 :b 2 :a 1) as long as you only use plist functions to access the plist. – tarsius Dec 16 '14 at 20:04
  • 1
    @Constantine: right, although neither plist-get nor plist-member seem to care if there are multiple identical keys. It looks like they behave analogously to alists in this respect: (plist-get '(:a "a" :b "b" :a "c") :a) ==> "a". Meanwhile, (plist-put '(:a "a" :b "b" :a "c") :a "d") replaces the value of the first :a key but not the second. – Dan Dec 16 '14 at 20:55
8

Org-mode, which is included with Emacs, has a plist merge function:

(defun org-combine-plists (&rest plists)
  "Create a single property list from all plists in PLISTS.
The process starts by copying the first list, and then setting properties
from the other lists.  Settings in the last list are the most significant
ones and overrule settings in the other lists."
  (let ((rtn (copy-sequence (pop plists)))
        p v ls)
    (while plists
      (setq ls (pop plists))
      (while ls
        (setq p (pop ls) v (pop ls))
        (setq rtn (plist-put rtn p v))))
    rtn))

To use it you'd need to (require 'org) first to load the file. Unfortunately, it's a very large file, 900+KB, so it's not really usuable as a utility library. Something like a standard plist package would be nice to have.

I started a very small one recently and realized that alists and plists are not treated the same, argument-wise - eg (plist-get LIST KEY) vs (assoc KEY LIST), which must be some unfortunate relic of optimization (or ?).

But, yes, Emacs does need a nice plist library - I didn't come across across one in my search, but it's still possible there's one out there somewhere, or else we'll have to start one and put it on Elpa/Melpa.

An alist library with the same interface would be nice to have also.

6

Reading the manual and browsing the list from C-u C-h a plist RET doesn't turn up any function to merge two property lists. The Common Lisp extensions don't provide any function specifically to act on property lists, only place (getf/setf/…) support. So you either need to rely on a third-party library or roll your own.

Rolling your own isn't too difficult. This implementation uses the last value in case of conflict.

(defun plist-merge (&rest plists)
  (if plists
      (let ((result (copy-sequence (car plists))))
        (while (setq plists (cdr plists))
          (let ((plist (car plists)))
            (while plist
              (setq result (plist-put result (car plist) (car (cdr plist)))
                    plist (cdr (cdr plist))))))
        result)
    nil))

(plist-merge '(:x 2 :y 3)
             '(     :y 0 :z 7))
=>            (:x 2 :y 0 :z 7)
  • nice. Why are you copy-sequence the first plist but not the others? And also, you can clean up the nesting a bit with cadr and cddr. – fommil Dec 25 '15 at 22:32
  • actually, org-combine-plists (below) is more or less the cleaned up version. I still don't understand why they copy-sequence the car. – fommil Dec 25 '15 at 22:37
0

I know this has been answered already, but incase anybody is interested, I took the org implementation and played a bit of code golf on it

(defun plist-merge (&rest plists)
  "Create a single property list from all PLISTS.
Inspired by `org-combine-plists'."
  (let ((rtn (pop plists)))
    (dolist (plist plists rtn)
      (setq rtn (plist-put rtn
                           (pop plist)
                           (pop plist))))))

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.