Elispで属性リストは他のリスト構造と違って参照渡しなので注意するんだ

Emacsの拡張の設定ファイルとかで属性リストが使われているのを見てから、
最近、自作の拡張とかで属性リストを使い始めてます。

今まで、key-valueな構造を作りたい時、ハッシュを使う程でもなかったり、
シリアライズしたかったりする場合は、連想リストを使ってたんですが、
属性リストの方が値の追加/更新が楽にできるしわかりやすいです。

(let ((e '(:hoge "a" :fuga "b")))
  (plist-put e :hoge "c")
  (plist-get e :hoge))                  ; => "c"

このように簡単にリスト形式で定義して使えるんですが、
属性リストは連想リストとかと違って参照渡しされるので注意しましょう。
つまり、

(defun testf (arg)
  (plist-put arg :hoge "c"))

(let ((e '(:hoge "a" :fuga "b")))
  (testf e)
  (plist-get e :hoge))                  ; => "c"

とか、

(let ((e '(:hoge "a" :fuga "b"))
      (f))
  (setq f e)
  (plist-get f :hoge)                   ; => "a"
  (plist-put e :hoge "c")
  (plist-get f :hoge))                  ; => "c"

のようになります。

これを回避するにはcopy-sequenceを使います。

(defun testf (arg)
  (plist-put arg :hoge "c"))

(let ((e '(:hoge "a" :fuga "b")))
  (testf (copy-sequence e))
  (plist-get e :hoge))                  ; => "a"
(let ((e '(:hoge "a" :fuga "b"))
      (f))
  (setq f (copy-sequence e))
  (plist-get f :hoge)                   ; => "a"
  (plist-put e :hoge "c")
  (plist-get f :hoge))                  ; => "a"

以上、経験者がお伝えしました。