jtwp470’s blog

日記とかプヨグヤミングとか

Emacs Lispを勉強したメモ

Emacsを使い始めて丸2年が経ちました.今までEmacsの設定関連はすべてネット上の情報を見てパクったりしていましたがいい加減Emacs Lisp構文を覚えておこうと思い,るびきちさんの「Emacs Lispテクニックバイブル」を読み始めました.とりあえずは基本構文のメモを残しておきたいと思います.

開発環境を整える

Emacs LispEmacs上で動くのでEmacsさえあれば問題ないのですが,カッコを多用した構文のため,何か入力支援がないと目が辛くなることは必至です.本に書いてあるものなどを導入しました.

  • M-:(eval-expression)でEmacsの下部にeval:というプロンプトが表示されそこでS式の評価ができる
  • scratchバッファでC-j
  • M-x lispxmpで式の値をソースコードに埋め込み

やはりいちばん最後のが無難でしたね.導入はぐぐればいっぱい出てくるので省略

代入

(setq 変数名 値)
(setq 変数名1 値1 変数名2 値2 ....)

グローバル変数として扱える

ローカル変数

ローカル変数を定義する.OCamlでもlet式で束縛できたがEmacs Lispでも同じようだ.

(setq x 1)              ; x = 1

(let ((x (+ 5 5))
       (y x)               ; y = x xはグローバル変数のほう
       )
   x                        ; x = 10
   y)                       ; y = 1

let式内部では(変数 値)変数 = 値を定義でき,値を書かなければただの変数としても定義できる. またletは一番内側で定義されている値が有効となり,letを抜けると外側の値が有効となる.

さらにlet*なる亜種もあり,これは直前の影響を受ける.

(setq x 1)
(let ((x 3) (y (1+ x)))
  (+ x y))                              ; => 5
(let* ((x 3) (y (1+ x)))
  (+ x y))                              ; => 7

データ型

C言語で言うところのintやcharのようなもの.更にデータ型に関してオブジェクトがデータ型であるかを調べる関数があり,「述語」という. ちなみにこれら述語は関数名の最後がpで終わる.(predicateの略)

  • 整数ならintegerp
  • 浮動小数点ならfloatp
  • 数値型ならnumberp

などなど

コンスセル,リスト,ベクタ

コンスセル

S式のペア

(値1 . 値2)

でできる.またcons関数を使っても良い.

(cons a b)

コンスセルの左側の要素を得るにはcar関数,右側を得るにはcdr関数を使う.

リスト

リストはコンスセルをどんどんつなげていったものと解釈して良いだろう. コンスセルを使うと次のように定義できる.

(cons 1 (cons 2 (cons 3 nil)))           ; => (1 2 3)

あとは次のようにも定義可能

(list 1 2 3)                             ; => (1 2 3)
'(1 2 3)                                 ; => (1 2 3)

さらにリストのN番目の要素を参照するための関数としてnth関数,elt関数がある.

(nth 1 (list 1 2 3))               ; => 2
(elt (list 1 2 3) 1)               ; => 2

ベクタ

vector関数でできる

比較

いまさらなんだが真偽値のうち真はt, 偽はnilである.

数値の比較

等しいかどうかは=1個で調べる.また等しくないことに関してはよくある!=ではなく/=であるので注意

;; 等しい
(= 10 10)                               ; => t
(= 10 20)                               ; => nil
(= 10 10.0)                             ; => t
;; 等しくない != ではなく /=
(/= 10 9)                               ; => t
(/= 10 10)                              ; => nil

同一性と同値性の比較

詳しくは書かないが同値比較にはequalsを,同一性比較にはeqを使う.

真偽値の反転

notで反転できる.つまり(not nil)tである.Emacs Lispでは偽はnilのみで空ベクタやゼロは真となる.

条件分岐

when, unless

条件分岐は真のときに真フォームを評価する式whenと対義語であるunlessが用意されている. 条件を満たさない場合はnilを返す.

(let (msg)
  (when (= 0 (% 6 2))
    ;; ここには複数のフォームが書ける
    (setq msg "6は偶数です"))
  msg)                                   ; => "6は偶数です"
;; whenの中が実行されない
(let (msg)
  (when (= 1 (% 6 2))
    (setq msg "6は奇数です"))
  msg)                                  ; => nil

if

ifは条件を満たしたときに真フォームを評価し,満たさない場合は偽フォームを評価する.ただこのifは偽フォームは複数あるのに対し真フォームに1つしかない.偽フォームは省略可能で条件を満たさなければnilを返すので実質whenのようなものである.

(if (zerop (% 6 2))
    "6は偶数"
  ;; 条件を満たさない場合は複数かける
  "6は奇数")                            ; => "6は偶数"

if + progn

ifでは真フォームが1つしかないが複数書きたいときはprognを利用してまとめてしまう.

;; ifとの連携
(let (a b)
  (if (= 1 1)
      (progn  ;; この部分が評価されている
        (setq a 2)
        (setq b 3))
    (setq a 10)
    (setq b 20))
  (list a b))                           ; => (2 3)

cond => if + progn

if + prognは頭が悪いのでもっとスッキリ書く方法としてcondがある.Schemeにもあるよね.

;; 見づらいのでcondを用いる
(let (a b)
  (cond ((= 1 1)
         (setq a 2)
         (setq b 3))
        (t
         (setq a 10)
         (setq b 20)))
  (list a b))                           ; => (2 3)

Schemeのcond式はelse節があるのに対しEmacs Lispには存在しない.糞.

そこでcondの条件式の最後の部分をtにすることでどれにも当てはまらなかったら最後を評価するというような形の式になる.

論理式

andやorが使える.

ループ

whilleループ

条件が真であるときにフォームを繰り返し評価する.

;; 0 ~ 2 のループ
(let ((i 0) result)
     (while (< i 3)
       (setq result (cons i result))
       (setq i (1+ i)))
     result)                            ; => (2 1 0)
;; 条件によるループ
(let ((lst '(1 2 3)) result)
  (while (car lst)
    (setq result (cons (car lst) result))
    (setq lst (cdr lst)))
  result)                               ; => (3 2 1)

リストの各要素のループ

リストの各要素に対して行うdolist式.これはループ変数とリスト式を取る.なお返り値は通常nilだが返り値も指定することができる.

;; リストの各要素に対して行うループ
;; (dolist (ループ変数 リスト式)) or (dolist (ループ変数 リスト 返り値指定フォーム))
(let (result)
  (dolist (x '(1 2 3))
    (setq result (cons x result)))      ; => nil
  (cons 'finished result))              ; => (finished 3 2 1)
;; dolistの返り値を指定する
(let (result)
  (dolist (x '(1 2 3) (cons 'finished result))
    (setq result (cons x result))))     ; => (finished 3 2 1)

関数の定義

関数の定義はdefunで行う.

(defun 関数名 (引数リスト)
  関数の中身)

省略可能引数を作りたい場合は省略可能引数の前に&optionalを書き,関数内でデフォルト値を設定しておく.

(setq 変数 (or 変数 値))

可変長引数は可変長引数の前に&restを置く.

これら引数がもしも定義されていない場合はnilとなる.

またlamda式を定義することもできる.これは「defun 関数名」をlambdaに置き換えるだけでok.

練習

とりあえずここまでで適当に想定した問題を解いてみる.

FizzBuzz

(defun fizzbuzz (num)
  (let (result)
    "Fizzbuzz in Emacs Lisp"
    (cond ((and (zerop (% num 3)) (zerop (% num 5)))
           (setq result "FizzBuzz"))
          ((zerop (% num 3))
           (setq result "Fizz"))
          ((zerop (% num 5))
           (setq result "Buzz"))
          (t
           (setq result num)))
    result))
(fizzbuzz 15)                           ; => "FizzBuzz"
(fizzbuzz 3)                            ; => "Fizz"
(fizzbuzz 5)                            ; => "Buzz"

if else if else の例

(defun hyouka (x)
  (let (score)
    (cond ((>= x 80)
           (setq score "A"))
          ((<= 60 x 79)
           (setq score "B"))
          ((<= 40 x 59)
           (setq score "C"))
          (t
           (setq score "D")))
    (message score)))
(hyouka 90)                             ; => "A"
(hyouka 70)                             ; => "B"
(hyouka 50)                             ; => "C"
(hyouka 10)                             ; => "D"

再帰関数の例

(defun fact (n)
  (if (= n 1)
      1
    (* n (fact (- n 1)))))
(fact 5)                                ; => 120

参考文献