mokeheheのScheme日記

ツッコミ、添削大歓迎です。いろいろ教えてください。

2008-01-13

[] Practical Common Lisp第12章 LISPと呼ばれる所以:リスト処理

  • 12章でようやくリスト、ってどんな異端LISP本だよ!(いい意味で)
  • 昔はリストだけで、vectorハッシュテーブル、ユーザ定義構造体などはなかった
  • 映画 Matrix のセリフに見立ててなんか書いてあるけど、意味がわからず…
  • car, cdr はハナモゲラだから、first, rest を使おうか
関数プログラミングとリスト
  • append は最後のリストはコピーしない
破壊的操作


[] Practical Common Lisp第11章 コレクションの続き

http://gigamonkeys.com/book/collections.html

正月休み以来、ずいぶん開いちゃったな…。

ソートとマージ
  • sort や stable-sort は破壊的なのか
  • だけど、
(setf my-sequence (sort my-sequence #'string<))

と書かなきゃイカンとな。渡したシーケンスが破壊されるということであって、元のシーケンスがソートされるというわけではない、のかな?xyzzyでは大丈夫なようだけど。

部分シーケンス操作
  • subseq で部分シーケンスが取れる、ので部分文字列の取り出しもこれでできる
シーケンス述語
  • every, some, notany, notevery
  • Common Lisp だと not.every とか not.some とか関数の合成が短く記述できないから、いちいち用意しないといけないのが辛いね
  • 合成する関数を書くとこんな感じかね、funcall と apply が怪しいんだけども
(defun combine (fn1 fn2)
  (lambda (&rest rest)
    (funcall fn1 (apply fn2 rest))))
  • not.every
(funcall (combine #'not #'every) #'evenp #(1 2 3 4 5))  ;=> t 
  • not.some = notany
(funcall (combine #'not #'some) #'evenp #(1 2 3 4 5))  ;=> nil  '
  • 合成はできるけど、funcall を書かないといけないのがメンドイから、
(defun my-notevery (pred &rest rest)
  (apply (combine #'not #'every) pred rest))

とかして定義を楽にするしかないんだろうなぁ。

シーケンス写像関数
ハッシュテーブル
  • make-hash-table でハッシュを作れるけどキーの比較関数が eql なので、文字列をキーにする場合はうまくいかない、ので (make-hash-table :test 'equal) で作ればよい
  • このサンプルや他のものでも、ハッシュのキーとしてシンボル「'foo」を使ってるけど、キーワードシンボルなら何度評価してもキーワードのままだからキーワードシンボル「:foo」を使うべきではないのかね?この辺の使い分けがよくわからんなぁ
  • ハッシュにキーがないのか値がnilなのかを判定するために、gethash で多値を返して、それを multiple-value-bind で受け取ってあれこれするのってメンドウじゃないかなぁ?それより haskey? みたいなのが楽なのでは?
ハッシュテーブル走査
トラックバック - http://sicp.g.hatena.ne.jp/mokehehe/20080113

2008-01-05

[] Practical Common Lisp第11章 コレクション

http://gigamonkeys.com/book/collections.html

  • Lispはリストだけじゃなくて、他の言語と同等に配列ハッシュも扱えるよ
  • vectorとlistは、より一般的な抽象化であるsequenceのサブクラスなので多くの共通点がある
vector
  • 固定サイズのvectorとリサイズ可能なvector
  • 固定:vector 関数で作成できる
  • より一般的な:make-array 固定も可変も作成可能、任意次元
  • :adjustable で実際にリサイズ可能に
vectorのサブタイプ
  • 文字列は要素がキャラクタに限定されたベクトルなので、次の章でベクトルを取る関数が文字列も受け取れることについて話す、とのこと。
  • make-array で、:element-type に 'character を与えれば、文字列が作れる
(make-array 5 :fill-pointer 0 :adjustable t :element-type 'character)  ;=> ""
シーケンスとしてのvector
シーケンスをなめる関数
  • いろんなキーワードで動作調整可能
    • たくさんあって複雑という気がせんでもない
高階関数の種類
シーケンス全体の操作


[] Practical Common Lisp第10章 数値、キャラクタ、文字列

[] Practical Common Lisp第9章 実践:ユニットテストフレームワークの作成

  • テスト結果は成功か失敗のどっちか
2つの最初の試み
  • 全部のテストをandでつなぐ
    • これだとどこで失敗したかわからない
  • 個々に成功するか失敗するかを出力させる
    • いちいち書くのがメンドイ、あと数が増えたら困る
リファクタリング
  • 個々の結果出力を1つの関数 report-result に変える
  • 成功したかどうかと式とで2回書かなきゃいけないのを、check マクロを使って手間を省く
戻り値の修正
  • report-result から戻り値を返すようにする
  • and マクロだと失敗した時点で処理を打ち切ってしまうので、打ち切らない combine-results マクロを作る
よりよいレポート結果
  • いくつかテストケースを組み合わて、増えすぎた場合に今のレポートだと多すぎて探すのが大変
  • グローバルにテスト名を保持する変数を作って、テストで設定し、結果表示する
抽象化あらわる
  • テストケースの関数名と、テストケース名で2回設定してるのがムダ
  • テスト関数だけど、それはあなたの心の中だけで、コードが特定のパターンにのっとっていること以外でテスト関数だとはわからない
  • 中途半端な抽象化はソフトウェア構築の安っぽいツールにしかならない
  • なぜなら中途半端な抽象化はパターンのマニフェストによってコードに表現されるので、大量のコードを複製する(メンテに問題が出る)ことで保障しなければならないからである
  • より微妙には、抽象化はプログラマの心の中だけにあるので、別のプログラマ(または未来の自分)に同じ抽象化を理解させる方法がないことである
  • 完璧な抽象化のためには、「これはテスト関数だ」ということを表現する方法と、パターンが必要とするすべてのコードが生成される必要がある
  • すなわちマクロ
  • deftest マクロを作る
テストの階層化
  • *test-name* をリストにすればいい
まとめる
  • 全部で26行!

ryos36ryos362008/01/06 10:44えらい勢いで読んでますね。すばらしい。私は CH3, CH23,CH24,ch26 を斜め読み程度。う~ん。

mokehehemokehehe2008/01/06 12:43英文をかなり適当に読み飛ばしてますからね。
Peter Seibel氏の英文がわかりやすく、説明がうまいですね。
ryos36 さんはSchemeやLispをすでにご存知のようですので、
この本では簡単すぎるのではないですか? (^^;;

トラックバック - http://sicp.g.hatena.ne.jp/mokehehe/20080105

2008-01-04

[] Practical Common Lisp第8章 マクロ:自分のを定義する

http://gigamonkeys.com/book/macros-defining-your-own.html

  • マクロ理解への障壁は、皮肉にもそれが言語によくマッチしてるからだ。マクロはちょっと変わった関数にしか見えない。
  • しかしマクロ関数とは異なるレベルであり、異なる種類の抽象化をもたらす
Macの話:単なる話

マクロがないLispを使ってた会社にMac氏が現れて、記述の置き換えの仕事をしていた、そしてそれはコンパイラに組み込まれましたとさ

マクロ展開時 vs 実行時
defmacro

マクロを書くステップ:

  1. マクロのサンプル呼び出し、展開されるべきコードを書く
  2. サンプルコールの引数から手で展開したコードを書く
  3. マクロの抽象化に漏れがないようにする
サンプルマクロ:do-primes
  • 素数でループする、do-primes を書く
  • loop を do で書くと
(defun primep (number)
  (when (> number 1)
    (do ((fac 2 (1+ fac)))
        ((> fac (isqrt number)) t)
      (if (zerop (mod number fac))
          (return-from primep nil)))))

(defun next-prime (number)
  (do ((n number (1+ n)))
      ((primep n) n)
    ))
  • いいかげんloopからdoへの書き換えはメンドイな。loop マクロが書ければいいんだけど。
マクロパラメータ
展開の生成
  • バッククォートを使えば、カンマやカンマアットでクォートを打ち消すことができる
    • カンマアットだとリストをその場に展開する
  • 展開をチェックするには、実行結果を見るか
  • macro-expand-1 を使って展開されたコードを見るか
    • macro-expand-1関数なので、クォートする必要がある
漏れを塞ぐ
  • Joel Spolskyの"The Law of Leaky Abstractions"の中で、抽象化をだいなしにしてしまう、詳細が漏れる抽象化のことを漏れやすい抽象化という用語を作り出した。
  • マクロも抽象化の方法だから、漏れのないようにしないとね
  • 現在の定義は漏れる可能性が3つある
(defmacro do-primes ((var start end) &body body)
  `(do ((ending-value ,end)
        (,var (next-prime ,start) (next-prime (1+ ,var))))
       ((> ,var ending-value))
     ,@body))
      • 残念なことに、この修正がマクロ抽象化に新たに2つの漏れを発生させる
    • 2. start と end の評価順が違う
(defmacro do-primes ((var start end) &body body)
  `(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
        (ending-value ,end))
       ((> ,var ending-value))
     ,@body))
    • 3. "ending-value" という変数名を使ってしまっている
      • package を使って解決もできるが、もっといい方法がある
      • getsym 関数は呼び出されるたびにユニークなシンボルを返す、これを使って
(defmacro do-primes ((var start end) &body body)
  (let ((ending-value-name (gensym)))
    `(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
          (,ending-value-name ,end))
         ((> ,var ,ending-value-name))
       ,@body)))

マクロ作成時には以下のルールに従え:

  • マクロ呼び出しのパラメータ順に評価しろ
  • 部分式の評価は一度だけにしろ
  • 展開時に使われる変数にはgetsymを使え

リンク:

マクロを書くマクロ
  • マクロを書くときは、マクロ展開時に使う変数を導入するために、大抵 let で始まる。ならこの定型処理をマクロ化しよう!
(defmacro with-gensyms ((&rest names) &body body)
  `(let ,(loop for n in names collect `(,n (gensym)))
     ,@body))
他の古くからあるマクロを書くマクロ:once-only
(defmacro once-only ((&rest names) &body body)
  (let ((gensyms (loop for n in names collect (gensym))))
    `(let (,@(loop for g in gensyms collect `(,g (gensym))))
      `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
        ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
           ,@body)))))
  • なにをやってるのかよくわからん
    • 一番外側の (let ...) で、once-only に与えられたシンボル用にgensymをして、
    • その内側の `(let ...) で1回だけ評価した値を格納するためのシンボルを作成、
    • その内側で once-only に与えられたシンボルを評価してテンポラリに格納
    • その内側で once-only に与えられたシンボルと同じ名前にバインドしなおす
  • 一番最初の (let ((gensyms ... で、gensyms というシンボルを使ってしまっているため、もしonce-onlyの外側でgensymsを使ってたら動作がおかしくなると思うのだが…。

g000001g0000012008/01/05 02:15はじめまして、自分もPCLを読んでみている者です。
自分はxyzzyはちょっと使ったことがある程度なのですが、
loopマクロはxyzzyにもlispディレクトリ以下に隠れてるらしいというのをどっかでみた記憶があったのを思い出して確認ちょっと確認してみました。
xyzzyだと(require "cmu_loop")で使えるようです。
cmuclから移植したのでこんな名前なのかもしれないですね。

mokehehemokehehe2008/01/05 07:19g000001さん、はじめまして。ブログ読ませてもらってます (^^;;
お書きのとおり、(require "cmu_loop") で本の中の loop が実行できました!ありがとうございます。

PCL を読むまで loop マクロというものを知りませんでした。
マクロだけでこんなムチャクチャできちゃうのはすごいですね。
loop は読みやすくていいですね。自分で書こうとは思いませんが (^^;;

DebraDebra2011/04/11 01:34u9f3XY Touchdown! That's a really cool way of putting it!

lytiwalytiwa2011/04/12 04:50ZECap3 <a href="http://srzjytyhymno.com/">srzjytyhymno</a>

yoshwamtyoshwamt2011/04/13 06:57UsHCYm , [url=http://fpvhnjgcsbyr.com/]fpvhnjgcsbyr[/url], [link=http://dujzatdeswnu.com/]dujzatdeswnu[/link], http://bgdvkybuwzjd.com/

rkmntuurkmntuu2011/04/13 06:57wGmkbz , [url=http://pidabylfpbmq.com/]pidabylfpbmq[/url], [link=http://xekbosghhjxg.com/]xekbosghhjxg[/link], http://hyfwxibcjdmv.com/

ckazhukckazhuk2011/04/23 03:24lcc4uc <a href="http://euvnhygtrqom.com/">euvnhygtrqom</a>

fvqnydtirfvqnydtir2011/04/24 11:46kkgpYy , [url=http://zdkboicpubmh.com/]zdkboicpubmh[/url], [link=http://zeliqhxdzinl.com/]zeliqhxdzinl[/link], http://ahsqzzzpbfdq.com/

トラックバック - http://sicp.g.hatena.ne.jp/mokehehe/20080104

2008-01-03

[] Practical Common Lisp第7章 マクロ

わずか7章でもうマクロキター

  • 条件式やガベージコレクションなど、Lispをオリジナルにする多くのアイディアが他の言語に影響を与えた一方、引き続き Common Lisp を特別なものにしている特徴は、マクロのしくみだ。
  • しかし、違う意味のマクロが広まってるおかげで、Common Lispマクロがいかにすばらしい特徴かを説明するのに苦労する
  • プログラミング言語を、言語コア+標準ライブラリと定義すると、理解しやすく実装しやすくなる
    • しかし、本当の利点は表現力にある
    • 言語をライブラリだけと考えれば、その言語は簡単に拡張できるでしょ?
  • マクロを使えば、新たな文法を作ることができる
when と unless
  • if の then, else には式をひとつしか与えられないので、それ以上の式を与えたい場合 progn を使う必要がある
    • if と progn を一緒にやってくれるのが欲しい
    • それ when でできるよ
(defmacro mywhen (condition &rest body)
  `(if ,condition (progn ,@body))) 
  • 取るに足らないマクロの例だけど、利点はでかい
cond
  • if ... else が複数つながると、インデントも深くなるし醜い
  • cond でスッキリ
and, or, not
  • and は偽が現れた時点で、orなら真が現れた時点で、残りの評価を打ち切る、のでマクロで定義される
ループ
  • Lispの25の特殊オペレータは直接ループ構造をサポートしてはいない、マクロが構築している
  • do: 汎用
    • dolist, dotimes: リスト、回数ループに特化したもの
  • loop: ミニ言語ぽい、Lispらしからぬ、英文ぽいループ用マクロ
    • 賛否両論
dolist と dotimes
do
(do (variable-definition*)
    (end-test-form result-form*)
  statement*) 
  • do を使ってフィボナッチ数の計算
オールマイティなloop
(loop
  body-form*)  
  • 複雑な使い方:
    • (loop for i from 1 to 10 collecting i) ==> (1 2 3 4 5 6 7 8 9 10)
    • (loop for x from 1 to 10 summing (expt x 2)) ==> 385
    • (loop for x across "the quick brown fox jumps over the lazy dog" counting (find x "aeiou")) ==> 11
  • (loop for i below 10 and a = 0 then b and b = 1 then (+ b a) finally (return a))
  • 22章で詳しく説明
  • loopはwhenやifよりちょっと複雑なだけで同じマクロだから、もしloopが標準ライブラリになかったとしても自分で実装できるよ
"Practical Common Lisp"を読んでる方:

[] Practical Common Lisp第6章 変数

http://gigamonkeys.com/book/variables.html

変数の基礎
レキシカル変数クロージャ
(let ((count 0)) #'(lambda () (setf count (1+ count))))
動的変数(特殊変数
  • defvar, または defparameter
  • 慣習的に名前を *...* とする
  • defparameter は常に値のセットを行う、defvar は変数が未定義の場合だけ
  • 実際には、defvar を使うべき
  • defvar や defparameter で定義した変数は特殊となり、動的変数になる。名前で見分けないと酷い目にあう。

よくわからん

定数
  • defconstant
  • 慣習的に名前は +...+
  • すでに定義済みの定数を違う値でもう一度定義しても値は変わらない
代入
  • setfマクロ
    • 格納先が変数の場合:setq 特殊オペレータ
  • ちょっとこれは理解不能だ~
(defun foo (x) (setf x 10))

(let ((y 20))
  (foo y)     ; foo の結果はyには反映されず
  (print y))  ; => 20

setf とか setq の、f とか q ってなんなのさ?

一般化された代入
  • 配列ハッシュテーブル、リスト、ユーザ定義データ構造も値を保持できる
  • どれも setf で値をセット可能
他の方法
  • incf, decf
  • push, pop, pushnew
  • (rotatef a b)
    • (let ( (tmp a)) (setf a b b tmp) nil)
  • (shiftf a b 10)
    • (let ( (tmp a)) (setf a b b 10) tmp)
トラックバック - http://sicp.g.hatena.ne.jp/mokehehe/20080103

2008-01-02

[] Practical Common Lisp第5章 関数

新しい関数の定義
関数パラメータリスト
オプションパラメータ
  • 関数引数リストに &optional をつける
  • デフォルト値を nil 以外にしたい場合は (param-name vaue) にする
  • 引数が与えられたかどうか知りたい場合は (param-name value supplied-p) とする
残りのパラメータ
キーワードパラメータ
(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p))
  (list a b c c-supplied-p))
上記のパラメータを混ぜて使うとき
関数戻り値
  • 関数の最後の式の値
  • return-from 特殊オペレータで
    • return-fromは関数に結び付けられるのではなく、block特殊オペレータから抜ける
    • defunは関数全体を同名のブロックで囲むため、関数から抜けることができる
  • return-from の最初の引数(ブロック名)はクォートの必要なし
  • 関数名を指定して return-from だと、関数名を変えたときに一緒に変えなきゃいけなくてメンドイ、けどreturn-fromを陽に使う機械はそんなに多くないので実際はたいして問題にならない
データとしての関数高階関数

xyzzy-lispでは loop が単なる無限ループなので、plotはdoを使って

(defun plot (fn min max step)
  (do ((i min (+ i step)))
      ((> i max))
    (dotimes (j (funcall fn i)) (format t "*"))
    (format t "~%")))
無名関数

[] Practical Common Lisp第4章 シンタックスとセマンティクス (2)

関数呼び出し
特殊オペレータ
  • 関数だと、呼び出される前に引数がすべて評価されてしまうので、ifなどが表現できない
マクロ

特殊オペレータとマクロって何が違うの?いっしょでよくない?

真、偽、そして等値性
  • eq: オブジェクト同値。実装依存なので、数値やキャラクタの比較に使うべきではない。
  • eql: 同じクラスのオブジェクトの2つの値が等しいか。同じクラスで値が等しい場合は真であることが保証される。別のクラスの場合等しくないことが保証される。
  • この本では常にeqlを使う
  • equal: リストや文字列の中身を見て比較してくれる。あとはeqlと一緒
  • equalp: 大文字小文字を同一とみなす。整数と浮動小数も。リストの要素同士がequalpの場合、真。あとはeqlと一緒
Lispコードの整形
  • 文法や意味的にではなく、コードの読み書きのためにインデント重要
  • 閉じ括弧は別の行に書くな
トラックバック - http://sicp.g.hatena.ne.jp/mokehehe/20080102

2008-01-01

[] Practical Common Lisp第4章 シンタックスとセマンティクス

てきとーに飛ばしつつ

なぜ括弧だらけなのか?
  • 最初は、人間が書くときはM式(meta-expression) で書いてそれをS式に変換するつもりだったけど、結局S式のほうが好まれた
ブラックボックスを開ける
S式
Lispの形としてのS式
  • リーダーがテキストをS式に変換した後、S式Lispコードとして評価される
  • キーワード(:)とクォートつけたシンボル(')の違いわかった。キーワードは評価してもキーワードのままだけど、クォートをつけたシンボルは評価すると変数の値になってしまうんだね
トラックバック - http://sicp.g.hatena.ne.jp/mokehehe/20080101

2007-12-31

[] Practical Common Lisp第3章

実践:シンプルなデータベース
  • 関数マクロとスペシャルオペレータ:ここでは説明しないけど、ちょっとずつ違うと思ってくれ
  • plist(プロパティリスト)
  • シンボル:名前だと思ってくれ
    • 「:」始まりでキーワードシンボルという、特殊なシンボルになるとは初めて知った
    • Rubyでのシンボルですね
  • (list :a 1 :b 2 :c 3) でplist作成
  • (getf (list :a 1 :b 2 :c 3) :a) でplistから取り出し
  • プロンプトを出してCD情報の入力補佐関数
    • *query-io* がよくわからず。(setf *query-io* t) 、(read-line) 、と変更
    • y-or-n-p はミニバッファにプロンプトが出て、キーを押した後も消えない
    • with-standard-io-syntax が定義されてない。無視。
データベースクエリ
  • (select :artist "Dixie Chicks") で探せるようにしよう
  • remove-if-not : 二重否定で分かりづらいけど、Haskell でいうところの filter
    • remove といっても、元のリストを変更するわけではなく、変更したものが戻り値として返る
(select (where :artist "Dixie Chicks"))
(select (where :rating 10 :ripped nil))

と書けるようにするのは見事としかいいようがない。

レコードの更新
(コードの)重複を削除して、大きく勝利
  • where 関数内で、要素ごとに下のように書いているのが美しくない!
(if title (equal (getf cd :title) title) t)
  • それに :title とか渡してるところで、渡してないものまで実行時にチェックするのがムダ
  • そこでマクロですよ
  • マクロコンパイル時に実行されるから、実行時にはノーコストだよ
  • (loop while fields ...) というのがエラー
    • (loop ...) は xyzzy では無条件ループ
    • (while fields ...) に変更
  • collecting とは?
    • loop~collecting で、結果をリストとして返す?
  • xyzzy lisp で make-comparisons-list はこんな感じかな?
(defun make-comparisons-list (fields)
  (let ((ls ()))
    (while fields
      (push (make-comparison-expr (pop fields) (pop fields)) ls))
    (reverse ls)))
  • macroexpand-1 で、マクロを展開した結果が見れる
(macroexpand-1 '(where :title "Give Us a Break" :ripped t))
#'(lambda (cd) (and (equal (getf cd :title) "Give Us a Break") (equal (getf cd :ripped) t)))

ここまでほとんど一切細かい文法や仕組みを説明しないのもすごいな。ある程度分かってる人前提?

Wrapping Up


補習
  • update中のコードの重複を取り除いてみる
  • (setf (getf row :title) title) のようなリストを作るマクロを作ればいい
  • where のときに習って:
(defun make-setf-expr (field value)
  (list 'setf (list 'getf 'row field) value))

(defun make-setfs-list (fields)
  (let ((ls ()))
    (while fields
      (push (make-setf-expr (pop fields) (pop fields)) ls))
    (reverse ls)))

(defmacro update (selector-fn &rest clauses)
  `(setf *db*
         (mapcar
          #'(lambda (row)
              (when (funcall ,selector-fn row)
                ,@(make-setfs-list clauses))
              row) *db*)))
  • defmacro 内の、selector-fn にカンマをつけるのがわからなくてハマッた
  • こうなると更に、where と update をまとめたくなるのう

りっぷるりっぷる2008/09/08 22:33>xyzzy lisp で make-comparisons-list はこんな感じかな?

こんなのは、いかがでしょうかー。

(defun make-comparisons-list (fields)
(if fields
(cons (make-comparison-expr (first fields) (second fields))
(make-comparisons-list (rest (rest fields))))
nil))

トラックバック - http://sicp.g.hatena.ne.jp/mokehehe/20071231

2007-12-30

[] Practical Common Lisp を読む

ようやく2章SLIMEという、Emacs上で動くLisp環境を使うらしい(SLIME: The Superior Lisp Interaction Mode for Emacs)。俺はxyzzyでやるよ。

  • 最初はEmacsの簡単な説明
  • REPL = Read-Eval-Print Loop

Practical Common Lisp を読んでる人:

トラックバック - http://sicp.g.hatena.ne.jp/mokehehe/20071230

2007-12-18

ryos36ryos362007/12/20 16:22私も買いました。買っただけ、、、になりそう。情報を交換しましょう。

mokehehemokehehe2007/12/22 14:18こちらこそ、よろしくお願いします!私はウェブで読んでます。
ようやく 1.Introduction, Who This Book Is For ですw

トラックバック - http://sicp.g.hatena.ne.jp/mokehehe/20071218