実践Common Lispを読む 第4章〜第6章
こんにちは。
今回は第4章〜第6章まで読みました。
内容が濃く、噛み砕いて書こうとすると分量が大変なことになるので、かい摘んでまとめていきたいと思います。
第4章 シンタックスとセマンティクス
シンタックス=文法、セマンティクス=意味、くらいでいいでしょうか。
Lispのシンタックスは「Lots of Irritating Superflous Parentheses(過剰でイライラさせる大量の括弧)」と蔑まれているが、Lisperには好まれています(?)。
その秘密に迫るために、Lispの仕様について詳しく見ていくということです。
*言語処理系の動作のブラックボックス
普通の言語のインタプリンタやコンパイラはブラックボックスが1つで、中身は字句解析器→抽象構文木→評価器みたいなっていますが、Lispは2つのブラックボックス(読取器と評価器)があります。
例:(foo 1 2) ("foo" 1 2) どちらもS式だが、前者だけがLispフォームとして意味がある
*関数呼び出し
基本式は
(function-name argument*)
S式では、最初にargumentを評価して、それからfunction-nameに引き渡す
それさえわかれば下の式の順序もわかりやすい
(* (+ 1 2) (- 3 4)) >-3
前置記法でも怖くない
*特殊オペレータ
関数みたいに考えると次の場合にxをうまく評価できない
なぜならxもargument*に含まれてしまうから
(if x (format t "yes")(fromat t "no"))
でも、これは特殊オペレータだから大丈夫らしい
特殊なオペレータは25種類
ifもそのうちの一つで、次のように使う
(if test-form then-form [else-form])
QUOTEも特殊オペレータ
(quote (+ 1 2))='(+ 1 2) >(+ 1 2)
LETも
(let ((x 10)) x) >10
*マクロ
マクロはS式を引数として取り、そのマクロフォームのかわりに評価されるLispフォームを返す関数である(?)
マクロフォームの評価は2段階
1.マクロフォームが評価されないままマクロ関数に渡される
2.マクロ関数が返すフォーム(マクロ展開)が通常の評価手順で評価される
マクロフォームの要素はちゃんとしたLispフォームでなくてよい
なぜなら、マクロは自分自身にローカルなシンタックスを定義するから
つまり、関数に似てるけどマクロは全然違うことをやっているよ!ってことらしい…
*等しさ
eq,eql,equal,equalpの4つがあるけど、「いつでもeqlを使え」
equalは等価性の判断基準が甘くてequalpはもっと甘い
*Lispコードの書式
書式について心がけること
- ネストしたらインデントをひとつ深くする:インデントの間違いでコードのミスがわかるからいいよ
- 括弧は独立せず行末で閉じる(「)))」みたいのを気持ち悪がらない。括弧は目立たないほうがいい)
第5章 関数
Common Lispは正確には関数型言語ではないし、Lisp系で最も「純粋な」関数型に近いSchemeでさえも、HaskellやMLに比べて純粋さが足りないらしい。
*defun
(defun name (parameter*) "省略可能なドキュメンテーション文字列" body-form*)
nameはだいたいアルファベットとハイフンで書く
ある値を変換するなら「->」を使うこともある
*オプショナルパラメータ
初期値をセットして、それが使われているかいないか知りたいとき
(defun foo (a b &optional (c 3 c-supplied-p)) (list a b c c-supplied-p)) (foo 1 2 ) -> (1 2 3 NIL) (foo 1 2 3) -> (1 2 3 T) (foo 1 2 4) -> (1 2 4 T)
*レストパラメータ
オプショナルパラメータを全部定義するのが大変な時は可変な&restを使えばいい
*キーワードパラメータ
&keyすると位置に依存しなくて便利
パラメータの併用は予期せぬ事態を起こすことがある
*データとしての関数
defunには
- 関数に名前をつける
- 関数オブジェクトの生成をする
という二つの役割がある
関数オブジェクトを通じて関数を起動するにはfuncallとapplyがある
- funcallは引数の個数が明確な場合に使い、最初の引数を起動する関数オブジェクト、それ以降を引数とする
- applyは最初の引数はfuncallと同じだが、リストを扱える
;以下は同義 (foo 1 2 3)=(funcall #'foo 1 2 3) ;関数オブジェクトfnを受け取って、そこで生成した値だけ*をプロットする (defun plot (fn min max step) (loop for i from min to max by step do (loop repeat (funcall fn i) do (format t "*")) (format t "~%")))
*無名関数
(lambda (parameters) body)
lambdaは関数オブジェクトの名前そのものだと考えられる
(funcall #'(lambda (x y) (+ x Y)) 2 3)->5
lambdaはクロージャを作成できる
第6章 変数
Common Lispは動的型付け
Common Lispにおける値は概念上は、全てオブジェクトへの参照
Lispは関数が呼ばれるたびに、その呼び出し元から渡された引数を保持するための新しい束縛(binding)を作る
*let
(let (variable*) body-form*) (let ((x 10) (y 20) z) ...)
各変数の初期化。Letは、xは10、yは20、zはNILに束縛する
関数のパラメータやLETの変数のスコープは、変数を導入したフォームによって束縛される
最も内側の変数の束縛が外側の束縛を隠す(shadow)
*レキシカル変数とクロージャ
クロージャは変数ではなく、束縛が補足される
(defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))
1つのクロージャには複数の変数の束縛も閉じ込められる
(let ((count 0)) (list #'(lambda () (incf count)) #'(lambda () (decf count)) #'(lambda () count))))
*ダイナミック変数
defparameterは評価されるたびに常に初期値を代入
defvarは変数が未定義だった場合に初期値を代入する(なくてもいい)
*代入
SETFの基本的なフォーム
(setf place value) (defun foo(x) (setf x 10)) (let ((y 20)) (foo y) (print y)) >20 >20
letに束縛されている
setfに関して
束縛に新しい値を代入しても他の束縛には何の影響も与えない。
代入前に束縛に保存されていた値にも何の影響もしない。
*一般化代入
どんなデータ構造でもsetfが使えるぞ
(setf x 10);単純な値 (setf (aref a 0) 10);配列 (setf (gethash 'key hash) 10);ハッシュテーブル (setf (field o) 10);'field'というスロット
*モディファイマクロ
(incf x) = (setf x (+ x 1)) (decf x) = (setf x (- x 1)) (incf x 10) = (setf x (+ x 10))
こんなこともできる
(incf (aref *array* (random (length *array*))))
副作用を持つかもしれない部分が一度だけ評価されるから、配列のある要素を気ままに増加させられる
setfで書き直すとちょっと大変
;radomで生成する値が同じになるかわからないからtmpに置いてる (let ((tmp (random (length *array*)))) (setf (aref *array* tmp) (1+ (aref *array* tmp))))
他のモディファイマクロ
;場所同士で値を入れ替えるrotatef (rotatef a b) = (let ((tmp a)) (setf a b b tmp) nil) ;左にずらすshiftf 最後の引数は最後から2番めの引数へと移動される値を提供 (shiftf a b 10) = (let ((tmp a)) (setf a b b 10) tmp)
まとめ
納得しながら読めたところと、理解が微妙なところがありましたが、ひとまず6章まで読みました。
それにしても、各話題に関して、かなり詳しく書いてあるので、リファレンスとしても使えそうな気がします。
特に面白かったのは、第4章のブラックボックスらへんです。なぜNILと'()が同じ意味なのか、ちゃんと解説されていてなるほど、と思いました。
けれど、クロージャはわかったようなわからないような…
内側の束縛が外側の束縛を隠すのはわかるんですが、漠然とした違和感がありますね。
あと、モディファイマクロも、パッと見では感覚的に理解できるのですが、setfに書き直すと複雑で驚きました。
とりあえず、メモ的な感じなので、わからなかったら本を読んでください。
次回はマクロです。