2008/06/04

朝一でときどきの雑記帖を見ていてクイズの存在に気が付いた。

与えられた木から、子→親への対応を作る

脊髄反射で回答を思いついたので仕事を始める前に書いてみた。都合15分弱。
(use srfi-1)

;; [name [tree ...]] -> [(tree-head . name)]
(define (child-parent ls)
(if (null? (cdr ls))
'()
(append-map
(lambda (l)
(cons (cons (car l) (car ls))
(child-parent l)))
(cdr ls))))
にしても、「10分で中級、30分で初級」というshiroさんによるレベル設定が絶妙すぎると思う。

ところでむしろ気になるのは、他人が回答に至るプロセスだ。自分はこんな感じの思考過程で上記の定義に至った。
リストのcarと、リストのcdrたちのcarとを、逆順にconsして集めるのね

とりあえずcdrたちに対してmap-appendか

そしてconsで再帰すればいいから、必然的に終了判定はnull?

"(if (null? (cdr ls))"あたりから書き始める
この場合は再帰も単純なので一発で書いちゃうけど、もうちょっと複雑な場合はテスト用のトリビアルな入力データをREPLで評価しながら関数に仕上げていく。あるいは、一発で関数を書いちゃってからテスト用のトリビアルな入力データを使ってREPLで評価し、意図していない結果であれば修正して(再帰の終了条件を修正する場合が多い)、これを繰り返して関数に仕上げていく。だいたいいつもこんな感じ。必要なら最後にCtrl-c tしてテストを作りますよ。gca.el最高!

ところがこのごろ、テストファーストという呪縛に悩んでいる。最初にテストを書け。問題にある「結果の例」とequal?になるテストケースを書けばいいのかな?でも結果のリストの順番はどうでもいいから、equal?になるテストケースをパスするコードを書くのは一苦労だぞ。そういえばGaucheにはリストを集合として同じだと見なす関数があったよな。しかし元の木に循環があったら集合としても同じにならないし……ってなことを考えているうちに、平気で30分は経過してしまう。

この問題はただのクイズだから気にしなくていい、というわけにもいかない。shiroさん自身も「仕事で扱った小ネタ」と書いているように、Schemeでプログラムを書いているときには、こういう小さな関数を大量にスクラッチしている。そのほとんどを書くときは、まず上記のような思考をまとめて、REPLで即興データの評価を繰り返しながら、徐々に関数としてまとめていく。その過程で関数のキワがはっきりしてくるから、そのきわどい条件を含むように即興データをどんどん変更していく。(場合によっては最後にCtrl-c t。)

テストファーストじゃなくてもメンテナンスのためにテストを残してけ、という意見もあるが、これもいまいち納得しきれていない(もちろん理解はできる)。gca.elを使って開発していれば、コードを書いちゃった後でテストを「残す」のは簡単だ。それでも、例えばプログラムの一部としてこの関数を使っていて、後で効率のいいコードにリファクタリングする場合、作り直したコードによって生成されるリストは現在の結果と同順になるとは限らない(その必要もない)。そうなったらユニットテストとして残しておいたテストケースの意味って、ゼロとは言わないけどあまりなくない?リファクタリングのときもテストデータをREPLで評価しながら作業するわけだけど、そのときにユニットテストとして残っているコードがテスト用として適切とは限らないんだよな。

自分は最近、入力と結果の型(っぽいもの)を関数にコメントしておくようにしている。リファクタリングするときは、この型(っぽいもの)の入力にそったデータをでっちあげて、それをREPLで評価するためのテストデータとして使う。そのでっちあげたテストデータは、やはり新しい関数のキワを突くようにどんどん変更していくだろう。人のコードを見るときも、まずはその情報を探す(コメントとしてであれ、コードとしてであれ)。

関数的なプログラミングにおけるテストの意味合いが、純粋によくわからない。なんども言うけど、テストそのものはCtrl-c tすれば作れるので、テストを残しておくことは面倒でもなんでもない。心にひっかかるのは、それって正しいの?という純粋な疑問だ。神様を毎朝拝むのは、一瞬なら面倒ではないかもしれないけど、自分にはそれが正しいことであると納得できないからしない。考えすぎかもしれないけど、何かを習慣として取り入れるなら、それなりに納得(理解ではなく)した上で取り入れたいということです。

No comments :