連想リストとは
連想リスト (Association List / alist) は (KEY . DATA)
なペアを要素とするリストのことを指します。Emacs Lisp では次のように書きます。
(setq editor '((name . "emacs") (version . 24) (options . ("-nw"))))
JSON データを解析するライブラリ json.el
はデフォルトではオブジェクト要素 {"KEY": DATA}
を連想リスト ((KEY . DATA))
に変換するため、Web API を叩くコードを書いていると連想リストのデータをよく見かけます。
例えば、Qiita の新着投稿 API から JSON データを取得してバッファにタイトルと URL を列挙するコードはこんな感じで書けます。
(defun json-load (src)
"ファイルまたはURLからJSONデータを読み込む."
(with-temp-buffer
(if (string-match "^https?://" src)
(url-insert-file-contents src)
(insert-file-contents src))
(json-read)))
(defun print-items (items)
"ITEMSを整形して出力する."
(cl-loop for item across items
do (let ((title (cdr (assq 'title item)))
(url (cdr (assq 'url item))))
(princ (format "Title: %s\n" title))
(princ (format "URL : %s\n" url))
(terpri))))
(with-output-to-temp-buffer "*Qiita*"
(print-items (json-load "https://qiita.com/api/v1/items?per_page=50")))
;;-> *Qiita* バッファに結果が出力される
問題点
上記のコードのように連想リストを参照する方法は assoc
(あるいは assq
) 関数と car/cdr
を利用するのが一般的ですが、(cdr (assoc 'title item))
というコードは正直あまり書きやすいとは言えません。他のプログラミング言語 によくある item.title
といったドット記法でさくっと書けたら楽なのに…と思うことがよくあります。
この辺りの話題は去年の今頃にもスタック・オーバーフローでも質問を投げてみたのですが、残念ながらあまり有効な回答は得られませんでした。 1
ただ、最近になってこの問題を解消してくれるライブラリをいくつか見つけた&思いの外使い勝手が良かったのでメモがてらに書き残しておきます。
解決策その1 let-alist.el
syntax: (let-alist ALIST &rest BODY)
let-alist.el は引数に与えられた連想リストを .KEY
のようなドットで始まるシンボルで参照できるようになるマクロを提供します。
これを利用すると、先ほどのコードの一部は次のように書き直すことができます。
(require 'let-alist)
(defun print-items (items)
"ITEMSを整形して出力する."
(cl-loop for item across items
do (let-alist item
(princ (format "Title: %s\n" .title))
(princ (format "URL : %s\n" .url))
(terpri))))
少しだけですが見やすくなりました。
let-alist
は参照するキーの数が多ければ多いほど、面倒なタイプ数を減らすことのできるライブラリともいえます。
特徴は以下のとおり
- ○ elpa (GNU Emacs の標準パッケージリポジトリ) で配布
- ○ Emacs25 からは標準で利用できる(予定らしい)
- ○ リストのネストした参照も可能 (
.foo.bar
) - ✗ 扱えるキーはシンボルのみ
- ✗ setf による再代入はできない
こんな具合にいくつかの制限はありますが、ほぼ標準なライブラリを使って直感的にコードが書けるという点はその不満を上回るくらいには便利かなと思います。
参考リンク
解決策その2 dash.el
syntax: (-let (((&alist key0 a0 ... keyN aN)) &rest BODY)
2
Emacs Lisp の定番ライブラリ dash.el には -let
という let
の拡張版マクロが含まれていて、パターンマッチ近いシンボルの束縛ができるようになります。また、連想リストだけでなくハッシュやプロパティリスト(plist)でも利用可能なため、こちらの方が汎用性は高いです。
これを利用すると、先ほどのコードの一部は次のように書き直すことができます。
(require 'dash)
(defun print-items (items)
"ITEMSを整形して出力する."
(cl-loop for item across items
do (-let (((&alist 'title title 'url url) item))
(princ (format "Title: %s\n" title))
(princ (format "URL : %s\n" url))
(terpri))))
let-alist
よりは若干タイプ数が多くなりますが、こちらも直感的に書けます。