Ros scriptでライフゲーム書いた。
lifegame.ros
に実行可能権限を与えたあと、./lifegame.ros
で起動する。
Enterキーを押すと一世代進む。 正整数を入力すると、その分がアニメーション付きで進む。
Ros scriptでライフゲーム書いた。
lifegame.ros
に実行可能権限を与えたあと、./lifegame.ros
で起動する。
Enterキーを押すと一世代進む。 正整数を入力すると、その分がアニメーション付きで進む。
まず、iotaを定義する。 簡潔にするため、オプション引数は使わない。
(defun iota (limit seed step) (if (> seed limit) nil (cons seed (iota limit (+ seed step) step))))
(> seed limit)
を一般化して、終了条件を指定できるよう変更する。
(defun func1 (pred seed step) (if (funcall pred seed) nil (cons seed (func1 pred (+ seed step) step))))
(+ seed step)
を一般化して、seedの変化を指定できるよう変更する。
(defun func2 (pred next-gen seed) (if (funcall pred seed) nil (cons seed (func2 pred next-gen (funcall next-gen seed)))))
最後に、mapcar関数と合成する。
(defun func3 (pred fn next-gen seed) (if (funcall pred seed) nil (cons (funcall fn seed) (func3 pred fn next-gen (funcall next-gen seed)))))
もっと単純に、こうしてもいい。
(defun func3 (pred fn next-gen seed) (mapcar fn (func2 pred next-gen seed)))
これでunfold関数の完成。
(defun unfold (p f g n) (if (funcall p n) nil (cons (funcall f n) (unfold p f g (funcall g n)))))
もうひとつのScheme入門 16. 継続を写経した。
「継続」は、計算において必ず存在する。 しかし、明示的に扱われることが少ない。
例えば、
(* 3 (+ 1 2))
のような計算があるとき、(+ 1 2)
を評価した後の残りの計算は、(* 3 _)
である。
この部分を継続と呼ぶ。
継続は、第一級関数をもつ言語であれば、CPS(継続渡しスタイル)によってあつかうことができる。
これは、関数が値を返す部分に、計算結果をどんな関数に渡すのかを引数で指定する方法。
(define (id x) x) (define (k+ a b k) (k (+ a b))) (define (k* a b k) (k (* a b)))
この関数群を使って、先ほど示したコードを書くとこうなる。
(k+ 1 2 (lambda (x) (k* 3 x id)))
関数適用の順番が入れ替わっている。
(define (fact n) (if (= n 1) 1 (* n (fact (- n 1))))) (define (kfact n k) (if (= n 1) (k 1) (kfact (- n 1) (lambda (x) (k (* n x))))))
一つ目が通常の階乗。二つ目がCPS階乗。 CPS版は末尾再帰になっている。 このように、再帰関数をCPS化することで、末尾再帰に変換できる。 Schemeは末尾再帰をループに最適化するので、とても嬉しい。
call/ccを使うと、継続によるジャンプができる。
> (* 3 (call/cc (lambda (k) (+ 1 2)))) 9 ;継続が呼ばれていないので、普通の式と同じ > (* 3 (call/cc (lambda (k) (+ 1 (k 2))))) 6 ;継続kが呼ばれ、(* 3 2)を計算し、トップレベルに6が返る
(define cc) (* 3 (call/cc (lambda (k) (set! cc k) (+ 1 2)))) > (+ 100 (cc 3)) 9 > (+ 100 (cc 100)) 300
継続を呼ぶと、その後の式を無視してトップレベルに戻る。
具体的な使用方法は後でまとめる。
問題のコードがこれ
(defun read-with-prompt () (princ "> ") (read))
CLISPではきちんと動く。
[1]> (read-with-prompt) > hoge hoge
SBCLではこうなる。
* (read-with-prompt) hoge > hoge
princよりも先にreadが実行されてしまう。
この問題は、SBCLの最適化が原因らしい。
(finish-output)
で出力の終了を明記するとうまく動く。
修正後のコード
(defun read-with-prompt () (princ "> ") (finish-output) (read))
下記のコードを書いている時にこの問題を発見した。
(declaim (inline function-name))
declaimはマクロ。 型宣言したり、インライン展開したり、コードの最適化に使われる。
http://cl.cddddr.org/index.cgi?%e6%9c%80%e9%81%a9%e5%8c%96#H-1yksz98dk7cep ここが詳しい。
これを買った。 表紙にいる緑色の怪物はLispエイリアンというらしい。 キモカワ。
ざっと見た感じ、すごいH本とかリーダブルコードみたいな雰囲気。
"はじめに"のイラストに爆笑した。 「お宅の床下にはストラウストラップの大きな巣があるようです 。」 Amazonのプレビューでも見れる。
軽快なノリと愉快な漫画でさくさく読み進めると、いつの間にかLispエイリアンが脳内に住み着く、そんな感じの本。 他のプログラミング言語の経験があって、Common Lispで遊んでみたいという人におすすめしたい。
また時間があいたら、Lispのマクロについてまとめたい。