本日午後8時よりLingr: Common Lisp部屋にて開かれる
「第2回 突発性CL勉強会@Lingr 8時だョ!全員集合」
楽しくROMできるように、そこで使われる題材
「良いLispプログラミングスタイル」
を少し予習をしておきました。
とはいえ、全体の半分くらいを適当に流し読みしただけですが(笑)
適当に、分からないこと、感心したことなどをまとめておきます。
*分からないところ*
悪い: 非慣習的
(defun add-to-list (elt lst)
(cond ((member elt lst) lst)
(t (cons elt lst))))
良い: 組み込み関数を用いる
(練習問題として残してある)
union使えばいいんでしょうか?
現在のLispの実装は、以下に対してはそれほど良くない:
- 小さなイメージのアプリケーションを配達する
- 実行時の制御(しかしGensymはそれを行なった)
この二つの意味がいまいち分かりません。
イメージや配達ってどういうこと?
EVAL-WHEN
eval-whenはよく取り上げられるけど名前しか知らない orz
Lispは以下に対して良い:
- 単一のプログラマ(または10人未満のチーム)のプロジェクト
これってLisp使える人が少ないだけ……というのとは違うんでしょうか。
コンディションとエラーの違いを理解する
CommonLispのエラーの仕組みというのを全く知らない。
R6RSの話を少し思い出した。
- 囲まれている小文字のテキストから目立たせるために、大文字でformat指示
子を置くことを考慮すること。
たとえば、"Foo: ~a"ではなく"Foo: ~A"とする。
- 有用なイディオムを学ぶこと。たとえば: ~{~A~^, ~}や~:pである。
- ~&と~%をいつ使うかを意識すること。
また、"~2%"や"~2&"も便利である。
単一の行を出力する多くのコードは~&で始まり、~%で終わるべきである。
逆に言えばチルダの後って大文字でも小文字でも大丈夫ってことでしょうか?
私は~Aと~%くらいしか知りません。「~&」って一体何者?
共通の抽象データ型からLispの実装へどのように対応づけるかを知る。
- キュー: tconc、(フィルポインタをもつ)ベクタ
tconcって何?
catchとthrowを使用に対するいくつかの忠告がある:
- マクロとしてより抽象的な制御構造を実装するときに副プリミティブとして
catchとthrowを用いること。通常のコードの中では用いないこと。
- ときどき、catchを設定するとき、プログラムはその存在をテストする必要
があるかもしれない。その場合、再起動がより適切かもしれない。
catch/throwはマクロで使う物であって通常のコードでは使うものではないってことでしょうか。
なんでこんなに嫌われて(?)いるんでしょうか?
悪い: インライン関数であるべき
(defmacro name-part-of (rule)
`(car ,rule))
インライン関数とマクロってどう使い分けるべきなんでしょうか。
- ジェネレーションスキャベンジングGCは、破壊的な更新によって遅くなり得
ることに注意すること。
世代別GCというやつのことでしょうか。
これだと作ってすぐに捨てるものが大量にあっても、あまり問題にならないというのは分かりますが、
これが破壊的な更新によって「遅くなり得る」とはどういうことでしょうか。
マルチタスクの環境ではしばしばもう少し注意深くなければな
らない。
(unwind-protect (progn form1 form2 ... formn)
(cleanup1 cleanup2 ... cleanupn))
- form1が実行されると仮定しないこと。
- formnが実行して完了することはないと仮定しないこと。
さっぱり分かりません orz
そういえば、マルチタスクのための構文ってCLに標準で入ってるんでしたっけ?
*感心したところ・驚いたところ*
型情報がわかっている場合、それを宣言すること。他の人々が行なうようなこ
とや、コンパイラが使うだろうとわかっているものだけを宣言するようなこと
を行なわないこと。コンパイラは変化し、プログラムが進行中の介在を必要と
せずにそれらの変更を自然に利用させたい。
また、宣言は人間の読み手とのコミュニケーションのためのものでもある -
単にコンパイラのためではない。
なんだかLispならではの意見ですよね。
型付けが動的なのに、型を宣言できるというのがなんとも。
型宣言があれば読みやすいというのには納得。
再ロード時に再初期化したくない物事に対してdefvarを使うこと。
defvarって再ロードしても値を書き換えなかったんですね。
(変数が未束縛のときのみ値を設定するらしい)
(catch 'robot-op
(let ((status (motor-status motor)))
(unwind-protect
(progn (turn-on-motor motor)
(manipulate motor))
(when (motor-on? motor)
(turn-off-motor motor))
(setf (motor-status motor) status))))
unwind-protectの引数のインデント。
第一引数だけインデントが少し深くなっていて、
なんかおかしい気もするけど、意味を考えたら物凄く読みやすい。
第一引数だけ特別な意味を持つような関数・スペシャルフォームには
こういったインデントを使うのがいい気がした。
クロージャをもつLispやその他の言語(たとえば、ML, Sather)は以下をサポー
トする:
- 制御抽象(イテレータやその他の新たな制御フロー構造を定義する)
高階関数を使うことだと思うけど、制御抽象って言い方はなんかカッコイイ。
(defstruct (rule (:type list))
name antecedent consequent)
こうすると構造体の実体がリストになるんでしたっけ。
なんかIRCの#Lisp_Schemeでこの話は聞いた覚えがあります。
再帰は再帰的なデータ構造に対して良い。多くの人々はリストを列として見る
ことを、そしてそれの上に繰り返しを用いることを好み、したがって、リスト
が先頭と残りに分割されるという実装の詳細を強調しないことを好む。
リストといえば、先頭と残りを分けるというイメージがありますが、
mapとかreduceとかの存在を考えると、やっぱり分割を強調しない方が
簡潔でいいということでしょうか。
表現力豊かなスタイルとして、末尾再帰がエレガントだとしばしば考えられて
いる。しかし、Common Lispは末尾再帰の除去を保証しないので、完全に移植
可能なコードの中では繰り返しの代替として使われるべきではない(Schemeの
中では問題ない)。
過去に末尾再帰をかなり使ったCommonLispのプログラムを
CLISPでコンパイルせずに動かそうとしたら、末尾再帰の最適化が成されてなかったみたいで、
実行時にスタックがあふれました。コンパイルしたらすんなり動きました。
—
つづきはWebで。