2010年4月11日日曜日

コーディング・スタイル

ちょっとしたツマラン話を。

On Lispに次のようなコードが紹介されてるんですよね。

CL-USER> (defun group (source n)
(if (zerop n) (error "zero length")) ;ここで暗黙のprognを利用している
(labels ((rec (source acc)
(let ((rest (nthcdr n source)))
(if (consp rest)
(rec rest (cons
(subseq source 0 n)
acc))
(nreverse
(cons source acc))))))
(if source (rec source nil) nil)))
GROUP
CL-USER> (group '(a b c d e f g) 2)
((A B) (C D) (E F) (G))

うん、まあ問題がない。当然動きます。
しかし、Scheme勉強している人は二行目が気にくわないのでは、と思います。

マクロdefunは暗黙のprognが含まれていて、defunの内部に記述されたS式は逐次実行され、最後のS式の評価値が返り値として返されます。それが故に2行目に

(if (zerop n) (error "zero length"))

なんて書き方が可能なんですよね。ここで(zerop n) => NILだとしても、この返り値はここでは無視されて、次のlabelsからの部分が実行されて関数groupは問題なく動くのです。

この暗黙のprognを利用した関数定義って良く見かけるんですよ。CLのコードやあるいはEmacs Lispのコードでは良くあります。Scheme弄ってる時間が長いと「何々なに?」とか思っちゃうんですが(笑)。と言うのも、論理的な話すると、

CL-USER> (defun group (source n)
(if (zerop n)
(error "zero length") ;論理的にはこれで良い
(labels ((rec (source acc)
(let ((rest (nthcdr n source)))
(if (consp rest)
(rec rest (cons
(subseq source 0 n)
acc))
(nreverse
(cons source acc))))))
(if source (rec source nil) nil))))
STYLE-WARNING: redefining GROUP in DEFUN
GROUP
CL-USER> (group '(a b c d e f g) 2)
((A B) (C D) (E F) (G))

上のように書いてもいっこうに構わないのです。ここではlabels以降は(zerop n) => Tの場合処理されないので、結局局所関数は作成されません。そして(zerop n) => NILの場合はじめて再帰関数が生成されるので、ifの第1引数、第2引数はほっぽったままです。多分こう言う書き方の方がSchemerの好みであって、また、Schemeではifの第3引数が省略された場合の返り値が未定義な以上、こっちの書き方の方が望ましいでしょうね。

第1の書き方はCLやEmacs Lispのコードだと本当に良く見かけます。恐らく、C言語でああ言う書き方で脱出試みる場合があるんで、その影響もあるんでしょうね。が、Schemerはそう言うスタイルは好まないとは思います。
あるいは、ifに全てを包めば、インデントが凄く深くなるのが嫌われている原因かもしれません。Schemerの方がCLerやEmacs Lisperほどインデントが深くなるのをさほど問題視していないのかもしれません。

ちなみに、あくまで論理的な話をすると、こう言う書き方の方がなおスッキリするやもしれません。

CL-USER> (defun group (source n)
(labels ((rec (source acc)
(let ((rest (nthcdr n source)))
(if (consp rest)
(rec rest (cons
(subseq source 0 n)
acc))
(nreverse
(cons source acc))))))
(cond ((zerop n) (error "zero length"))
(source (rec source nil))
(t nil))))
STYLE-WARNING: redefining GROUP in DEFUN
GROUP
CL-USER> (group '(a b c d e f g) 2)
((A B) (C D) (E F) (G))

このように、実行形態を最後にまとめちゃっても、結果は同じですよね。
ただ、このスタイルの場合、最初に局所関数を作っちゃうでしょうから、その辺をCLerの人たちは「メモリの無駄」ってんで嫌ってるのかもしれません。

0 件のコメント:

コメントを投稿