リストと連想配列 - 環境設定のための Emacs Lisp 入門

Emacs Lisp 入門の第 4 回目です。

Lisp の名前は LISt Processing (リスト処理) から取られているぐらい Lisp ではリストがよく使われます。 今回はそのリストとリストで作られた連想配列(連想リスト) について説明します。

リストの概要

リストというのは値の集まりです。
設定でよく使うリストは、 ロードするファイルの検索パスを格納している load-path などでしょう。 *scratch* バッファーで評価すると load-path の中身を見ることができます。
load-path
("~/elisp" "d:/emacs/lisp"  ...)
この load-path などのリストに要素を追加する場合、 書籍やサイトによっていろいろな書き方がされているのを見たことはないでしょうか。
それらの書き方が理解できるようにリストについて説明していきます。

リストの作成

リストを作成する場合には list 関数やクオート(') を使います。
(list "foo" "bar" "baz" "quz")  ; ("foo" "bar" "baz" "quz")
'("foo" "bar" "baz" "quz")      ; list と同じ
(setq xlist '("foo" "bar" "baz" "quz")) ; リストを xlist の変数に格納
このリストには違う種類の値を格納でき、その子要素としてリストを取ることもできます。
(list 1 "foo" t xlist '(2 3)) ; (1 "foo" t ("foo" "bar" "baz" "quz") (2 3))
'(1 "foo" t xlist (2 3))      ; クオートの場合はリストの中もクオート
                              ; (1 "foo" t xlist (2 3))

リストと式

シンボルにするクオートが何故リストを作るときにも出てくるのか 疑問に思う方は次のように考えて下さい。
  1. () はもともとリストの表記
  2. リストは S 式(先頭要素を関数、残りを引数)として評価
  3. クオートすると評価を抑制するので、リストのまま
こう考えられる証拠にリストとして作成したものを評価(eval)すると S 式として実行します。
(setq foo '(+ 1 2))  ; (+ 1 2) というリスト
(eval foo)           ; リストを評価 => 3
elisp では式というコードもリストというデータも同じものです。 これが Lisp の統一感の一端です。

コンスセルとリスト構造

リストを扱うのに必要になるため、リストの構造も少し解説します。

elisp にはコンスセルという値のペアを格納するデータ構造があります。 これは cons 関数やクオートして直接構造を書く方法で作成できます。
(cons "foo" 1)  ; ("foo" . 1)
'("foo" . 1)    ; cons と同じ
コンスセルに格納する値はなんでもよく、このコンスセルを繋げていくことによってリストはできています。
(setq xlist '("foo" "bar" "baz"))
elisp_guide_list.png

リストの操作

設定でよく行うリストの操作は要素の追加だと思います。 リストの操作について追加を中心に説明していきます。

追加の基本

追加する場合は次のようなことを行っています。
  1. 要素を追加したリストを新しく作成
  2. 作成したリストを変数に格納し直す
(setq test-list '("foo" "bar")) ; テスト用のリストを用意

(setq test-list
      (push "baz" test-list))   ; ("baz" "foo" "bar")

add-to-list による追加

追加して、 setq してというのも面倒なので add-to-list という関数も用意されています。 これには同じ要素は追加しないという機能もあります。
ただし、変数の中身を変えるので、 引数として渡すリストの変数はクオートしておく必要があることに注意して下さい。
(add-to-list 'test-list "baz")  ; ("baz" "foo" "bar")

コンスセルを使った追加

前章で紹介したようにリストはコンスセルからできています。 このコンスセルを使ってリストに要素を追加することもできます。
(setq test-list 
      (cons "baz" test-list))   ; ("baz" "foo" "bar")

末尾への追加(リストの結合)

リストの構造上、要素を追加する場合はこのコンスセルでの追加が基本のため、 要素を先頭に追加する関数が多いです。 しかし、リストの順番に意味があることも多く、リストの後ろに追加したいことがあります。 リストの末尾に追加するには append を使います。
ただし、 append は正確にはリストの結合なので、 追加する要素もリストにしておく必要があります。
(setq test-list 
      (append test-list '("baz"))) ; ("foo" "bar" "baz")

(setq test-list 
      (append '("baz") test-list)) ; ("baz" "foo" "bar") 先頭に追加もできる
リストへの追加方法をいくつか紹介しましたが、 自分で設定に使う場合には、どれか一つ覚えておけば十分でしょう。
個人的には一番応用が効くのでこの append がお勧めです。

リスト要素の削除

あまり利用頻度は高くはありませんが、リスト要素の削除方法も説明しておきます。

リストの要素を削除する場合には delete を使います。
(delete ELT SEQ)
delete はリスト(SEQ)の中から指定した値(ELT)に一致する要素を削除したリストを返します。 一致する要素がなければ、リストをそのまま返します。
追加と同様に変更されたリストを変数に入れなおして使います。
(setq xlist '("foo" "bar" "baz"))
(setq xlist 
      (delete "foo" xlist)) ;; ("bar" "baz")

連想配列(連想リスト)

連想配列というのはキーと値のペアを格納したコンテナーであり、キーから値を検索できるようになっています。
これは他の言語によってはマップ、ディクショナリーなどと呼ばれるものです。 因みにハッシュは連想配列の実装方法の一つです。

elisp では連想配列はリストで実現されていて、連想リスト(Associative list, alist) とも呼ばれます。 これは(キー . 値) のコンスセルを要素としたリストです。
なお、リストの要素がコンスセルなのであって、 先ほどのリストがコンスセルからできているのとは別の話です。

連想リストは assoc 関数を使ってキーが一致する要素をリストから取り出すことができます。
(setq test-alist '(("foo" . 1)
                   ("bar" . 5)
                   ("baz" . 10))) ; (("foo" . 1) ("bar" . 5) ("baz" . 10))
(assoc "bar" test-alist) ; ("bar" . 5)
この連想リストも設定で使うことがあります。 中でもよく使うものは auto-mode-alist だと思います。 これはファイルを開いた時のメジャーモードを決める変数で、 正規表現とモードの連想リストです。
auto-mode-alist
(("\\.\\(cmd\\|bat\\)$" . cmd-mode) ("\\.jl\\'" . julia-mode) ...)
連想リストの場合も追加などの操作は通常のリストと同じです。
;; add-to-list
(add-to-list 'auto-mode-alist '("\\.dart\\'" . dart-mode))

;; コンスセル
(setq auto-mode-alist (cons '("\\.mirah$" . ruby-mode) auto-mode-alist))

;; apppend
(setq auto-mode-alist
      (append auto-mode-alist
              '(("\\.xul$" . sgml-mode)
                ("\\.xaml$" . sgml-mode)
                )))
削除の場合は assoc で取得した要素を delete の指定に使います。
(setq auto-mode-alist
      (delete (assoc "\\.js\\'" auto-mode-alist) auto-mode-alist))

連想配列はキーにシンボルを使えば C の構造体のような役割も担えます。 実際、 JavaScript では連想配列とオブジェクトは同じものです。
elisp で使うデータ構造はほとんどリストと連想配列です。 連想配列がリストとコンスセルからでき、 リストもコンスセルからできていることを考えると データの構成要素はコンスセルだけです。
さらにリストと式は同じものでした。 今回の記事で Lisp のシンプルさと統一感を少し感じとってもらえたらと思います。


関連記事
スポンサーサイト
Prev.    Category    Next 

Facebook コメント


コメント

コメントの投稿

Font & Icon
非公開コメント

このページをシェア
アクセスカウンター
アクセスランキング
[ジャンルランキング]
コンピュータ
35位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
6位
アクセスランキングを見る>>
カレンダー(アーカイブ)
プルダウン 降順 昇順 年別

05月 | 2017年06月 | 07月
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 -


はてな新着記事
はてな人気記事
ブロとも申請フォーム
プロフィール

yohshiy

Author:yohshiy
職業プログラマー。
仕事は主に C++ ですが、軽い言語マニアなので、色々使っています。

はてブ:yohshiy のブックマーク
Twitter:@yohshiy

サイト紹介
プログラミング好きのブログです。プログラミング関連の話題や公開ソフトの開発記などを雑多に書いてます。ただ、たまに英語やネット系の話になることも。