http://rubikitch.com/に移転しました このページをアンテナに追加 RSSフィード

2010-02-01

[]Emacs Lisp基礎文法最速マスター

基礎文法最速マスターブームにのっかってみる。Ruby基礎文法最速マスターをだいたいEmacs Lispelisp)に置き換えてみる。

Emacs LispEmacsで使われているLisp方言のひとつだ。他の言語をある程度知っている人はこれを読めばEmacs Lispの基礎をマスターしてEmacs Lispを書くことができるようになるかもしれない。無保証ではあるが。

ある程度はCommon Lispにも応用できると思うよ。

更新情報

  • [2010/02/03]
    • 注釈の誤りを修正。
    • let*について加筆。

基礎

対話的にEmacs Lisp式を評価する

Emacsを起動したときに生成されている、スクラッチバッファ(*scratch*)を使うと、Emacs Lispの式を簡単に評価することができる。M-Tabで補完できるぞ。

式を書いたらC-jを押すと、式の値が出てくるぞ。

(+ 1 3)
4
(format-time-string "%Y/%m/%d %H:%M:%S" (current-time))
"2010/02/01 22:53:34"

あるいは M-x ielm を起動すると、シェルirbインタラクティブRuby)のようなインターフェースが使えるぞ。 Tabキーでの補完もきく。

*** Welcome to IELM ***  Type (describe-mode) for help.
ELISP> (* 5 4)
20
ELISP> (buffer-size)
95
ELISP> 

xmpfilterが好きなRubyistはlispxmpをどうぞ→xmpfilter のような自動注釈を Emacs Lisp で実現する lispxmp.el をリリース - http://rubikitch.com/に移転しました


表示

エコーエリアに表示するにはmessage関数を使う。message関数はformat関数の書式文字列が使える。format関数は、ぶっちゃけて言うとsprintfだ。

(message "ans = %d" (+ 3 4))    ; ans = 7と表示される
変数の宣言

Emacs Lisp変数宣言をしなくても代入した時点ですぐに使える。が、Emacs Lispファイルを書くときはdefvarで変数宣言をしておくとバイトコンパイラを黙らせることができる。

(defvar foo 1)              ; 変数fooを1に初期化する
(setq bar 10)               ; 変数barに10を代入する

ユーザカスタマイズを目的とする変数を宣言するにはdefcustomを使う。詳しくは C-h f defcustomをどうぞ。

ローカル変数を使うときは、letを使う。ここではローカル変数aとbを用意し、両者を足している。

(let ((a 1)
      (b 2))
  (+ a b))                              ; => 3

letにはlet*という亜種がある。let*は前に設定されたローカル変数の影響を受ける。let*を使うべきところでletを使ってしまうとハマってしまうので注意だ。

(setq x 1)
;;; letは同時にバインドされるので外側のxを参照する
(let ((x (+ x 3))
      (y (+ x 2)))                      ; この時点でのxは1
  (+ x y))                              ; => 7
;;; let*は直前のローカル変数代入の影響を受ける
(let* ((x (+ x 3))
       (y (+ x 2)))                     ; この時点でのxは4
  (+ x y))                              ; => 10

ただし、ローカル変数はダイナミックスコープなので、ローカル変数の値は呼び出した関数にも波及するので注意!!

(defun f ()
  a)       ; ローカル変数のaを参照してしまう
(let ((a 12))
  (f))                                  ; => 12
コメント

コメントは「;」から行末まで。

コメントの用法はセミコロンの数によって使い分ける。

;;;; 主要な部分のヘッダ(左端に揃える)

;;; 関数定義の外側で使い、
;;; プログラムの設計原理を説明する(左端に揃える)
(+ 3
   ;; このコメントは字下げに揃える
   (buffer-size))                        ; バッファのサイズ

実行

Emacs Lispファイルを「実行」するとは、ファイルをロードすることである。

たとえば、foo.elをロードするには

M-x load-file /path/to/foo.el

あるいは、

M-x load-library foo

を実行する。


数値

整数小数が使える。

1
1.234
四則演算
(+ 1 1)                                 ; => 2
(- 1 1)                                 ; => 0
(* 1 2)                                 ; => 2
;; 整数どうしの割り算は整数になる
(/ 5 2)                                 ; => 2
;; どちらかが小数なら結果も小数
(/ 5.0 2)                               ; => 2.5
;; 余りをとる
(mod 5 2)                               ; => 1
インクリメンタルとデクリメント

インクリメンタルはincfマクロを、デクリメントはdecfマクロを使う。

マクロなので、Emacs Lispファイルで使うときは (eval-when-compile (require 'cl)) にしたほうが無難。くわしくはこちら→Emacs Lispプログラマはガンガン(require 'cl)しろよ - http://rubikitch.com/に移転しました


(require 'cl)
(let ((a 0))
  ;; インクリメンタル
  (incf a)                              ; => 1
  (incf a)                              ; => 2
  ;; デクリメント
  (decf a)                              ; => 1
  )

1だけ加減するにはそれぞれ1+関数と1-関数を使う。

(let ((b 3))
  (1+ b)                                ; => 4
  (1- b)                                ; => 2
  )

文字列

Emacs Lisp文字列はダブルクォートで囲む。\t(タブ)や\n(改行)などの特殊文字を使うこともできる。シングルクォート文字列は用意されていない。式展開も用意されていない。

"abc"                                   ; => "abc"
"abc\tdef\n"                            ; abc タブ def 改行
"\\"                                    ; \ 1文字
"\"foo\""                               ; "foo"
文字列操作
;;; 結合
(concat "aaa" "bbb")                           ; => "aaabbb"
(mapconcat 'identity '("aaa" "bbb" "ccc") ",") ; => "aaa,bbb,ccc"

;;; 分割
(split-string "aaa,bbb,ccc" ",")        ; => ("aaa" "bbb" "ccc")

;;; 長さ(文字数)
(length "abcdef")                       ; => 6
(length "あいうえお")                   ; => 5

;;; 長さ(文字幅)
(string-width "あいうえお")             ; => 10

;;; 切り出し(第3引数のインデックスは直前なので注意)
;; 0〜2番目の直前
(substring "abcdef" 0 2)                  ; => "ab"
;; 0〜最後から2番目の直前
(substring "abcdef" 0 -2)                  ; => "abcd"
;; 0〜最後の直前
(substring "abcdef" 0 -1)                  ; => "abcde"
;; 2番目から最後まで
(substring "abcdef" 2)                     ; => "cdef"

;;; 検索 (正規表現、文字列の順に指定する)
(string-match "bc" "abcd")              ; => 1

リスト

Lispにも配列はあるものの、リストの方が基本的なのでリストを。

リストはlist関数を使う。リテラルリストするには「'(要素 ...)」でもよい。

(setq ary '(100 200 300))               ; => (100 200 300)
(setq ary (list 100 200 300))           ; => (100 200 300)
要素の参照と代入
(setq ary (list 100 200 300))

;; 最初の要素を参照するにはcarかfirst関数を使う。
(setq a (car ary))                      ; => 100
(setq a (first ary))                    ; => 100
;; nthかelt関数を使うと任意の要素を取り出せる
(setq a (nth 0 ary))                    ; => 100
(setq a (elt ary 0))                    ; => 100
(setq b (nth 1 ary))                    ; => 200

;; リストを書き換えるにはsetfとnthを併用する
(require 'cl)
(setf (nth 0 ary) 1)                    ; => 1
(setf (nth 1 ary) 2)                    ; => 2
;; 確かに書き変わっている。
ary                                     ; => (1 2 300)
要素の個数

リストの要素にもlength関数が使える。

(length ary)                            ; => 3
リストの操作
(setq ary (list 1 2 3))                     ; => (1 2 3)
;; 先頭を取り出すにはpopマクロを使う
(require 'cl)
(pop ary)                               ; => 1
ary                                     ; => (2 3)
;; 先頭に追加するにはpushマクロを使う
(push 5 ary)                            ; => (5 2 3)
;; 末尾を取り出す(非破壊的)
;; データ構造上、リストでは末尾を取り出すことは稀。
(last ary)                              ; => (3)
(car (last ary))                        ; => 3
ary                                     ; => (5 2 3)
;; 末尾に追加するにはリスト化してappendする
(setq ary (list 5 2))                   ; => (5 2)
(append ary (list 9))                   ; => (5 2 9)
コンスセル

コンスセルとは、2つのデータのペアであり、リストの構成要素となっている。コンスセルの左側をcar、右側をcdrと言う。名前は歴史的理由からなので深く考えなくてよい。

(setq a 1)
(setq b 2)
;; コンスセルはcons関数で作成する
(setq c (cons a b))                     ; => (1 . 2)
(setq c '(1 . 2))                       ; => (1 . 2)
(car c)                                 ; => 1
(cdr c)                                 ; => 2

;; cdrにコンスセルを指定し、数珠繋ぎにするとリストになる。
;; 最後のコンスセルはnilで終える。
(setq l (cons 1 (cons 2 (cons 3 nil))))        ; => (1 2 3)
(setq l (list 1 2 3))                          ; => (1 2 3)
;; よって、リストのcar/cdrを取るとこうなる。
(car l)                                        ; => 1
(cdr l)                                        ; => (2 3)

ハッシュ

Emacs Lispにはハッシュリテラルは用意されていないのでmake-hash-table関数を使う必要がある。(めんどくせぇ)しかも表示形式が用意されていないので泣けてくる。

;; ハッシュの値を表示するために自分で関数を定義する必要がある…はぁ
(defun print-hash (hash)
  (with-temp-buffer
    (loop initially (insert "{")
          for k being the hash-keys in hash using (hash-values v) do
          (insert " " (prin1-to-string k) " => " (prin1-to-string v) ",")
          finally   (delete-backward-char 2) (insert " }"))
    (buffer-string)))

;; ハッシュの作成(テスト関数はequalにしておくのが無難)
(setq hash (make-hash-table :test 'equal)) ; => #<hash-table 'equal nil 3/65 0x11472f30>
;; 要素の代入
(puthash "a" 1 hash)    ; => 1
(puthash "b" 2 hash)    ; => 2
(print-hash hash)       ; { "a" => 1, "b" => 2 }
(puthash "c" 5 hash)    ; => 5
(puthash "d" 7 hash)    ; => 7
(print-hash hash)       ; { "a" => 1, "b" => 2, "c" => 5, "d" => 7 }
;; 要素の参照
(gethash "a" hash)      ; => 1
(gethash "b" hash)      ; => 2

;; キーの取得(loopマクロが必要)
(require 'cl)
(loop for k being the hash-keys in hash collect k) ; => ("a" "b" "c" "d")
;; 値の取得
(loop for v being the hash-values in hash collect v) ; => (1 2 5 7)
;; ペアを得る(長い!!!)
(loop for k being the hash-keys in hash using (hash-values v)
      collect (cons k v)) ; => (("a" . 1) ("b" . 2) ("c" . 5) ("d" . 7))
;; キーの存在確認(gethashしかない?)
(gethash "a" hash)      ; => 1
;; ハッシュのペアの削除
(remhash "a" hash)      ; => nil
(print-hash hash)       ; { "b" => 2, "c" => 5, "d" => 7 }

制御文

Emacs Lispでは、条件文で偽と見なされるのはnilのみで、それ以外のオブジェクトは全て真と見なされる(0や空文字列も真)。

条件式
;; if式のelse部分は複数の式を置くことができるのでちょっと不釣合い
(setq a 1)
(if (equal a 1)
    'ok
  'ng)                                  ; => ok
(if (equal a 2)
    'ok
  1                                     ;複数の式が置ける
  'ng)                                  ; => ng
;; cond式(ifのthenも複数の式を書きたければcondを使うほうがいい)
(cond ((equal a 1)
       'ok)
      (t
       'ng))                            ; => ok
(cond ((equal a 2)
       0
       'ok)
      (t
       1
       'ng))                            ; => ng
;; condならばelse ifがあっても大丈夫
(setq a 0)
(setq b 2)
(cond ((equal a 1)
       "foo")
      ((equal b 2)
       "bar")
      (t
       "baz"))                          ; => "bar"
繰り返し
;; while式 (けっこう使われているが正直ダサい)
(setq i 0)
(while (< i 5)
  i                                     ; => 0, 1, 2, 3, 4
  (incf i))

;; リストを繰り返すにはdolistかmapcを使う
(dolist (i '(1 2 3))
  i                                     ; => 1, 2, 3
  )
(mapc (lambda (i)
        i                               ; => 1, 2, 3
        )
      '(1 2 3))

;; 回数繰り返し
(dotimes (i 5)
  i                                     ; => 0, 1, 2, 3, 4
  )

;; 無限ループは (while t 〜) で書ける

その他の繰り返し

Rubyでいうイテレータ関数clパッケージにごっそり入っているのだが、cl関数Emacs Lisp的には使うなと言われている*1ので、loopマクロを推奨する。 loopマクロは (eval-when-compile (require 'cl)) と入れてればいつでも使える。 loopマクロの他の使用例→http://rubikitch.com/に移転しました

みんなもloopマクロ使おうぜ!

(require 'cl)
;; 条件に合うものだけを選ぶ
(remove-if-not 'evenp '(1 2 3 4 5))     ; => (2 4)
(loop for x in '(1 2 3 4 5)
      when (evenp x)
      collect x)                        ; => (2 4)
;; 条件に合うものを除く
(remove-if 'evenp '(1 2 3 4 5))         ; => (1 3 5)
(loop for x in '(1 2 3 4 5)
      unless (evenp x)
      collect x)                        ; => (1 3 5)

;; 条件に合う最初のものを返す
(find-if 'evenp '(1 2 3 4 5))           ; => 2
(loop for x in '(1 2 3 4 5)
      when (evenp x)
      return x)                         ; => 2

;; 等しい値があるか調べる
;; member関数は組み込み関数なので使える
;; 等しい値がある場合はリストの残りを返す
(member 3 '(1 2 3 4 5))                 ; => (3 4 5)
(member 30 '(1 2 3 4 5))                ; => nil
;; findはcl関数
(find 3 '(1 2 3 4 5))                   ; => 3

;; 条件に合うものがあるか調べる
(loop for x in '(1 2 3 4 5)
      thereis (evenp x))                ; => t
(loop for x in '(1)
      thereis (evenp x))                ; => nil

;; 全ての要素が条件に合うかを調べる
(loop for x in '(1 2 3 4 5)
      always (evenp x))                 ; => nil
(loop for x in '(2 4)
      always (evenp x))                 ; => t

;; 条件に合うものの個数を数える
(count-if 'evenp '(1 2 3 4 5))          ; => 2
(loop for x in '(1 2 3 4 5)
      count (evenp x))                  ; => 2

;; 最大のものを返す(組み込み関数)
(max 1 2 3 4 5)                         ; => 5

;; 最小のものを返す(組み込み関数)
(min 1 2 3 4 5)                         ; => 1

;; 加工結果が最大のものを返す
;; これだと加工後の値を返してしまう 
(loop for x in '(1 -2 3 4 -5)
      maximize (abs x))    ; => 5
;; めんどい(^^;
(let* ((x '(1 -2 3 4 -5))
       (score (mapcar 'abs x))
       (value (apply 'max score))
       (i (position value score)))
  (nth i x))                            ; => -5

;; 加工結果が最小のものを返す
;; これだと加工後の値を返してしまう 
(loop for x in '(1 -2 3 4 -5)
      minimize (abs x))                 ; => 1
;; めんどい(^^;
(let* ((x '(1 -2 3 4 -5))
       (score (mapcar 'abs x))
       (value (apply 'min score))
       (i (position value score)))
  (nth i x))                            ; => 1

;; 昇順にソートする(組み込み関数)
(sort '(1 -2 3 4 -5) '<)                ; => (-5 -2 1 3 4)

;; 加工結果で昇順にソートする
(sort* '(1 -2 3 4 -5) '< :key 'abs)     ; => (1 -2 3 4 -5)

;; 加工結果を配列で返す(組み込み関数)
(mapcar 'abs '(1 -2 3 4 -5))            ; => (1 2 3 4 5)

関数=サブルーチン

(defun sum (x y)
  (+ x y))
(sum 1 2)                               ; => 3

ファイル入出力

Emacs Lispの場合、ファイルバッファに読み込んでから加工する。

;; test-bufferという名前のバッファを作成し、いろいろな内容を書き出す
(with-current-buffer (get-buffer-create "test-buffer")
  ;; バッファの内容を空にする
  (erase-buffer)
  ;; /tmp/foo.txtの内容を挿入する
  (insert-file-contents "/tmp/foo.txt")
  ;; 文字列を挿入する
  (insert "End\n")
  ;; バッファの内容をファイルに書き出す
  (write-region (point-min) (point-max) "/tmp/bar.txt"))

無名関数高階関数

Emacs Lisp高階関数が使える。つまり、関数引数にすることもできるし、関数を返り値にすることもできる。

関数引数に渡すには、関数シンボルを指定するか、無名関数λ式)を指定する。

mapcar関数は、リスト加工結果を返す頻出関数だ。第1引数には要素を引数とする加工関数を指定するようになっている。

たとえば、リストの全要素を絶対値にしたもののリストを返すには、abs関数を加工関数とするため、次のように書ける。

(mapcar 'abs '(1 -2 3 4 -5))            ; => (1 2 3 4 5)

これをλ式で書くと以下のようになる。

(mapcar (lambda (x) (abs x))
        '(1 -2 3 4 -5))                 ; => (1 2 3 4 5)

(lambda (x) (abs x))がλ式である。λ式の文法は、上で述べた関数定義の「defun 関数名」を「lambda」に置き換えたものである。λ式は、名前がつけられていない関数をその場に埋め込むのに使う。Rubyのブロックみたいなもの。


関数変数の説明を見る

Emacsにはビルトインでドキュメント機能がついている。つまり、EmacsにおいてわからないことはEmacsに聞けってことである。

関数の説明はC-h fで見られる。

変数の説明と現在の値はC-h vで見られる。

他の基礎文法最速マスターへのリンク

*1:くそったれが…

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証