Emacsを書く前のLisp

  • 47
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Emacs Lispを書き始める前に知っておくと良いLispの基礎について。この記事を読んでもEmacsのスキルは上がりません。あと、話の段取り上、めんどくさい方法から順に説明することがあるよ。

対象読者

  • むかしの自分
  • Emacsを使ってるけどLispについてちょっとは知っておきたいひと

リスト

(りんご ばなな みかん)のように要素を並べたものがリスト。データ構造に詳しくないひとは「配列みたいなもの」だと思ってもいい。厳密には連結リスト(linked list)なのでちょっと違ふんだけど、まあ似たようなもの。(あとで説明します)

ちなみに、()の中に要素を空白で区切ってならべるリストの書きかたを「S式 (S-expression)」と呼ぶ。Lispのリストは原則としてこの方式で書かれるけど、べつに覚えてなくてもいいです。

リストによるプログラミング

プログラムとして評価したい式を(+ 1 2)のように並べて書く。もちろんこれはリストで、+の引数として1, 2を渡すことを意味する。C言語やJavaScriptなどの言語では1 + 2のような演算子式が言語構文として用意されるが、Lispでは基本的に区別はない。

そして、+はただの函数だ。

実行環境

Lispを実行する環境として、Emacsの起動時に*scratch*といふバッファが作成されてるはずなので、C-x b *scratch*で移動してみよう。C-x C-f hoge.elのようにテスト用に.el拡張子のファイルを作成してもいい。

また、M-x ielmで対話環境を起動することもできる。好きな方を利用しよう。

(+ 1 2)

*scratch*またはテスト用のファイルで式を実行したいときは、)の直後にカーソル移動してC-x C-eと押してみよう。

scratchバッファでLispを実行する様子

実行結果は画面下部に表示される。Emacsはリストを解釈して、なんらかの結果を返してくれる。これを評価(evaluation)といふ。

クォート

最初に書いたように(りんご ばなな みかん)はリストだ。ところがこれを評価しようとすると、「そんな函数はない」と怒られる。

(りんご ばなな みかん)を評価した様子

それでは、「プログラムとしてのリスト」と「データの並びとしてのリスト」を区別できない。

ので、データとしてのリストを表現したいときはクォートといふ仕組みを利用する。リストをクォートするには(quote …)でリストを括ってやる。

(quote (りんご ばなな みかん))

(りんご ばなな みかん)を評価した様子

データとしてのリストを、プログラムとして評価し直すこともできる。

リストをプログラムとして評価する様子

Lispがプログラムをリストとして表現できることには意味がある。Lispで書かれたコードは、プログラムとデータの世界を行ったり来たりすることができる。

函数

Emacs Lispではdefunで函数定義することができる。

(defun trapezoid (height a b) ; 函数名 (引数リスト)
  "台形の面積を求める."           ; 説明文字列
  (* height (/ (+ a b) 2)))

せっかくなのでPHPで、まったく同じコードを書いて比較してみる。

/**
 * 台形の面積を求める.
 */
function trapezoid($height, $a, $b)
{
    return $height * ($a + $b) / 2;
}

defunの中では、height, a, bといふ変数が利用できる。ここで「変数」といふ概念が初めて現れたが、Lispの式の中に現れる文字の並びを シンボル と呼ぶ。ここではtrapezoid height a bがシンボルだ。シンボルを評価すると、変数の値になる。ここはLisp以外の言語と同じっすね。

PHPのコードでは演算子の結合度の違ひがあるので($a + $b)は括弧で括らないといけないのでバグの温床になるが、Lispでは二項演算子を利用しないので問題ない——と強弁すれば主張できないことはないけど、実際は算数の授業とかで習ってきた書きかた(中置記法)の方が馴染みがあるし、ほかの言語で実装されたアルゴリズムを実装するときに困らないので無理に慣れようとする必要もない。

最後に、返り値について。CやPHPなどのようにreturnのようなものはない。リストの最後に評価された値が返される。

幸ひにもEmacsには中置記法で書くこともできる。

(defun trapezoid (height a b) ; 函数名 (引数リスト)
  "台形の面積を求める."           ; 説明文字列
  (string-to-number
   (calc-eval "$ * ($$ + $$$) / 2" nil height a b)))

nilについては後で説明する。$ $$ $$$は変数のプレースホルダで、それぞれ第一引数、第二引数、第三引数… と増えていく。(もちろん10個引数があれば$$$$$$$$$$になるのは難点…)

Lispプログラミングに役立つモード

show-paren-mode

ここまでコピペせずに手で書き写してくれた型がいらっしゃるかはわからないが、()の対応の把握に四苦八苦したりはしなかっただろうか。

そんなときは、そんなときじゃなくても、show-paren-modeは有効にしておくといい。

emacs-show-paren-mode.gif

カーソル位置にある括弧の対応関係がわかりやすくなるので、Lispじゃない言語でのコーディングでも便利だが、Lispでは特に重要度が高いので常時onにしておくと良いだろう。

自分の~/.emacs.d/init.el~/.emacsファイルに一行書いておくだけでいい。

init.el
(show-paren-mode 1)

余談だが、一部の人の間では始まり括弧(を「カッコ」、閉じる括弧)を「コッカ」と呼ぶ文化がある。 文化…?

eldoc

Emacsには、入力中の函数がどのような引数を取るのか、あと現在どの引数を入力中なのかを視覚的に把握するための機能が付いてる。

emacs-eldoc.gif

これも、init.elに一行書くだけでいい。

init.el
(add-hook emacs-lisp-mode-hook 'turn-on-eldoc-mode)

そのほか

標準でインストールはされてないけど、ParEditは非常におすすめ度が高い。慣れると非常にたやすくリストを操作できるようになる。日本語のParEdit チュートリアルがあるので、習熟するにはあまり手間はかからないだろう。

変数

話が脱線してしまった。脱線する前には函数定義の話で、引数として渡されてきた変数についても言及した。

一時変数

let*式を利用すると、ローカルでのみ有効な変数を作ることができる。

(let* ((a (+ 2 3 4))
       (b (* a a a)))
  (insert (format "\nresult = %d, %d" a b)))

変数が有効なのは(let* …)で囲まれた中だけで、実行が終了すると変数a bは参照できなくなる。

このあたりから「どこでスペース何個ぶんインデントすればいいんだ…」と途方に暮れはじめる頃なのだけれど、実際のところEmacsではTABまたはC-iを押せば勝手にいい感じにしてくれる。「そろそろ位置を調節したいな」って気分になってきたらポンとキーを押すだけでいい。それだけ。

変数の変更

変数の値はsetで変更することができる。(set symbol value)のような式を評価すると、symbolのシンボルをvalueに束縛することができる。

用語として出てきた束縛(bind)なのだけれど、代入(assign)と同じだと思って問題ない。

だが待ってほしい、シンボルを引数として渡さなければいけないのだ。シンボルはそのまま書くと変数として評価されてしまふので、クォートして渡さなければいけない。つまり、次のように!

(let* ((x "a"))
  (insert x)
  (set (quote x) "b")
  (insert x))

いや、さすがにいちいち(quote v)とか書くのはめんどくさいだろ… ってことで、もっと簡単な方法がある。

(setq x "b")

これでいい。むしろ、ふつうはsetよりもsetqの方が頻繁に利用される。

特別な値

Lispで一番の特別な値としてnilがある。この値にはいくつかの役割があるのだけれど、いちばんわかりやすいのは偽値だろう。

(eq 1 2)  ;=> nil
(eq 2 2)  ;=> t
(not t)   ;=> nil
(not nil) ;=> t

せっかくなので、ついでにifも紹介しておく。

Lispで奇偶判定してる様子だよっ

C言語などで三項演算子(条件演算子)を使ったことがあれば、まったく同じものだと気付くだろう。

クォート

(quote x)とか書くのは実際めんどくさい。ので、省略記法がある。

(eq (quote Python) 'Python) ;=> t

(equal '(りんご ばなな みかん)
       (quote (りんご ばなな みかん))) ;=> t

(eval '(+ 1 2 3)) ;=> 6

Lispに触れずにEmacsのカスタマイズをしてきたなら、こちらの記法は馴染みがあるかもしれにあ。実際こちらの方がよく利用する——といふよりも、ふつうはsetqsetはあまり利用しない。

余談だけれど、日本語ではsetqは「せっとく」と読まれる。うそじゃないよ竹内先生もそう言ってるよEmacsを説得するんじゃ。

リスト

リストはEmacsでもよく利用されるデータ構造である。その根本にあるのはとてもシンプルなアイディアだけれど、とても複雑なデータ構造をも表現することができる。

リストを作るには

Lispでデータとしてのリストを作るには、いくつかの方法がある。

  • リストをクォートする
  • list函数で書く
  • コンスセルを組み立てる

リストをクォートする方法はいままで書いてきた通りで、(quote (1 2 3))または'(1 2 3)と書きます。`(1 2 3)と書くこともできるけれど、これはもっと別の機能があるので後で紹介する。

list函数で書く方法はとてもおてがる。(list 1 2 3)と書くこともできるし、以下のように変数を利用することもかんたんにできる。

(let ((a 2)
      (b 5)
      (c 6))
  (quote (a b c)) ;=> '(a b c)
  (list a b c))   ;=> '(2 5 6)

コンスセルを組み立てる方法については次に書く。

空リストとは

(くう)とは何ぞ?」 ——とは禅問答のような問だが、(から)リストとはいったい何者なのか。

わからないときはEmacsさんに質問だ。

M-x ielmで(quote ())を評価するとnilが現れるのだ

なんと、リストだと思ったらnilが現れてしまった。嘘だと思ったら自分で(eq nil '())とか評価してみてね。

セル

Lispでの「リスト」とは、いはゆる連結リスト、それも単方向リストにあたる。C言語の入門の講義などで実装させられたことがあるかもしれないが、Lispでははじめから組み込みで、しかも簡単に利用できる。

その根幹にあるのは「セル」と呼ばれるデータ構造で、歴史ある実際には大した意味がない用語を伴って「コンスセル」とも呼ばれる。

;;   CAR部  CDR部
(cons  'a    'b)

consはただの函数で、引数として渡した二つの要素を持つセルを作り出す。前の方を「CARカー」、後の方を「CDRクダー」と呼ぶ。これは変な名前だが歴史的経緯の産物で、現代においては特に意味はない。

セルからリストへ

さて、空リストはnilだと言ったが、一要素のリストとは何か? 解答を書いてしまふと次の通りだ。

(cons 'a nil)

;; 本当にそうなのか? 自分で動かして試してみよう。
(equal (list 'a)
       (cons 'a nil))

(equal (quote (a))
       (cons 'a nil))

いままでいくつか紹介してきた方法は、cons函数を使っても表現することができる。また、リストの中で(a . b)のように.を付けて書くと、consのように2要素が組になったセルを作ることができる。これを「ドットつい (dotted pair)」と呼ぶ。

なんと以下の式は、すべて同じリスト(a b c)として評価される。

(cons 'a (cons 'b (cons 'c nil)))
(cons 'a (list 'b 'c))

(quote (a . (b . (c . nil))))
(quote (a b c . nil))
(quote (a . (b c)))
(quote (a b c))

(list 'a 'b 'c)
'(a b c)

リストの皮剥き

データを並べたからには、取り出したくなるだろう。セルのCAR部とCDR部を取り出すのは、そのままcarcdrだ。

(car '(a b c))             ;=> a
(cdr '(a b c))             ;=> (b c)
(car (cdr '(a b c)))       ;=> b
(cdr (cdr '(a b c)))       ;=> (c)
(car (cdr (cdr '(a b c)))) ;=> c

実用的なプログラムを書くのにいちいちこんなことをやってたのではノイローゼになりかねない!

もちろん、もっと便利な函数はいくつもある。

(first '(a b c))  ;=> a
(second '(a b c)) ;=> b
(third '(a b c))  ;=> c
(fourth '(a b c)) ;=> nil
(fifth '(a b c))  ;=> nil

(nth 0 '(a b c)) ;=> a
(nth 1 '(a b c)) ;=> b
(nth 2 '(a b c)) ;=> c
(nth 3 '(a b c)) ;=> nil
(nth 4 '(a b c)) ;=> nil

firstnthは数がずれてることには注意しよう。nthの方はC言語の配列と同じだと思っておけばいい。

連想リスト

リストはとてもシンプルなデータ構造だけれど、前の項目のように数字でアクセスするだけじゃなく、任意の値をキーにして中身を取り出すこともできる。これは、ほかの言語では「連想配列(associative array)」などと呼ばれるが、それに似た使いかたができるので「連想リスト(association list)」と呼ばれる。

(assoc 'hoge '((hoge . 1) (fuga . 2))) ;=> (hoge . 1)
(assoc 'fuga '((hoge . 1) (fuga . 2))) ;=> (fuga . 2)

assoc函数では、引数として渡された値をキーにして、リストの各要素のCARが一致すれば、そのドット対を返す。値が欲しければ、その外側をさらにcdrで囲ってやればいい。

データ構造について興味関心があれば、連想リストのメリットとデメリットについて想像してみると良いですね。今回は紹介しないけど、Emacs Lispにはハッシュテーブルもあるよ…

連想リストと応用

assocに似た函数にassoc-defaultがある。こちらのおもしろい点として、比較函数を指定できることがある。(また、assocはドット対を返すがassoc-defaultはCDR部だけを返す)

string-matchは正規表現パターンにマッチしなければnilを、マッチしたらその位置を返す函数だ。

(string-match "AB?C+D" "ACCCD") ;=> 0
(string-match "AB?C+D" "CCCD")  ;=> nil

この二つを組合せると、どうなるか。

(assoc-default "/path/file.md"
               '(("\\.txt\\'" . my-text-mode)
                 ("\\.rb\\'" . my-ruby-mode)
                 ("\\.md\\'" . my-markdown-mode))
               'string-match)

"/path/file.md"の部分を"/path/file.rb""/path/file.py"のように変更すると、どのように動作が変るか確認してみよう。

さて、この仕組みは実際にEmacsがファイル名からメジャーモードを決める仕組みそのものだ。

属性リスト

属性(attribute/property)とは、あるものの持つ共通した性質のことで、哲学的な説明については属性 - Wikipediaを読んでいただくとして、HTMLの「属性」やJavaやPHPなどのクラスの「プロパティ」と同じような概念だと思っておいて良い。

果物の値段と出荷日のリストをむりやりHTMLで表現すると、こうなるだろうか。(おそらく、このあとJavaScriptで処理されるのだろう…)

<ul class="fruits">
   <li class="fruit" data-price="250" data-ship-date="2015-10-21">りんご</li>
   <li class="fruit" data-price="300" data-ship-date="2016-01-14">みかん</li>
   <li class="fruit" data-price="300" data-ship-date="2016-03-08">ばなな</li>
</ul>

Lispでは次のような単純な形式のリストで表現することができる。ちなみに:abcのように:から始まるシンボルはEmacs Lispでは「定数」として予約済みで、クォートせずに利用することができる。

(list
 '(:果物 "りんご" :値段 250 :出荷日 "2015-10-21")
 '(:果物 "みかん" :値段 300 :出荷日 "2016-01-14")
 '(:果物 "ばなな" :値段 300 :出荷日 "2016-03-08"))

そして、そのようなときに利用できるのがplist-get函数だ。属性のキーと値が交互に並んだリストから「いい感じ」に値を取り出してくれる。

(let ((apple '(:果物 "りんご" :値段 250 :出荷日 "2015-10-21")))
  (format "%s:%d円 (%s 発送)"
          (plist-get apple :果物)
          (plist-get apple :値段)
          (plist-get apple :出荷日)))
;=> "りんご:250円 (2015-10-21 発送)"

ここまでに、ほかの言語の連想配列に相当するものとして、「連想リスト(association list)」を紹介した。しかし実際のところ、RubyやPHPなどの言語では属性リストに近い用途でHashクラスや(連想)配列が利用されることがある。

このあとのLisp

古い本なのでもう既に新品では売ってないのだけれど、リスト遊び -- Emacs で学ぶ Lisp の世界ではこの記事と同様にEmacs Lispを通じてLispを学ぶことができる。(@kazu_yamamoto先生におかれましては電子書籍でも良いので再販していただきたく、何卒、何卒…)

また、魔法言語 リリカル☆LispはインタラクティブにLispを学ぶことができる素晴らしいゲームだ。ふざけた名前のように見えるかもしれないけれど内容は本当に良くて、私はこのゲームのせいでLispに興味を持ってしまった。ただし、Emacs Lispとは仕様がちょっと異なる点については注意。

さらにそのあとについては、インターネット上にもいろいろなヒントがあるので読んでみたり、Emacs Lispパッケージを書いてみたり、あるいはほかのLispを触ってみるのも良いかもしれない。

私は書籍では「Scheme手習い (The Little Schemer)」が大好きなのだけれど、これは人によって感想の差が激しくて、のめりこむうんざりするかの両極端のように感じる。本屋か図書館で何章か読んでみてから購入した方が良いだろう。

この記事のタイトル通りにEmacs Lispについて学びたい型は、英語だけれどGNU Emacs Lisp Reference Manualを読めばなんとかなる。また、日本語の書籍ではEmacs Lispテクニックバイブルがあり、(現在では多少事情が異る箇所はあるものの)Emacs固有の技術的な事情についてかなり詳細に記述されてるのでおすすめ。

そのほかEmacs Lisp の情報源 - Qiitaにまとめられてる。