id:eel3:20090307:1236401934 のCommon Lisp版の自作iotaにはSchemeっぽさが残っているというか、Common Lispっぽさが足りていないと思う。
Schemeは末尾再帰の最適化が仕様に含まれているけどCommon Lispでは含まれていない、というのは有名な話で、だから末尾再帰を最適化してループに展開して欲しい場合は処理系依存の機能に頼ることになる。
しかしそんな軟弱な態度じゃマズイだろう、ということでCommon Lispではループ用のマクロが多用される*1。
ということで、ループマクロの勉強を兼ねて書き直してみた。
まずはdotimes。iotaの第1引数はcountという名前だから、やはり繰り返す回数が大切なんだろうと考えて、dotimes。
; dotimes 版その01 ; let が余分な感じに見える (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (let (nums) (dotimes (i count) (push (+ start (* step i)) nums)) (nreverse nums))) ; dotimes 版その02 ; 戻り値を指定できることに気づいたので変更 (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (let (nums) (dotimes (i count (nreverse nums)) (push (+ start (* step i)) nums))))
名前の通り、単純に指定回数だけループする場合に向いている。ただ今回の場合、letでレキシカル変数を用意する必要があって、その分だけ何となく面倒に感じる。ループ構文側でレキシカル変数を用意したいのだけど、dotimesでは無理そうだ。
次はdo。dotimesより自由度が高くて、色々な書き方ができるみたい。
; do版その01 ; 取り敢えずリファレンス片手に書いてみた (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (do ((i 0 (1+ i)) nums) ((>= i count) (nreverse nums)) (push (+ start (* step i)) nums))) ; do版その02 ; ループ本体をなくしてみた (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (do ((i 0 (1+ i)) (nums nil (push (+ start (* step i)) nums))) ((>= i count) (nreverse nums)))) ; do版その03 ; その02がイマイチに感じたので、変数を増やしてみた (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (do ((i 0 (1+ i)) (n start (+ n step)) (nums nil (push n nums))) ((>= i count) (nreverse nums))))
ループ本体をなくした書き方が可能だけど、その分書き方に気をつけないと読みにくくなりそうだ。
最後はloop。ループキーワードに手を出してみた。
; loop版 ; 何というかもうLispじゃない感じ (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (loop for i from 0 below count collect (+ start (* step i))))
ループキーワードを使ったloopは、何というかスゴイと思う。その代わりLispっぽさが消えるけど。これは賛否両論あるのも納得できる。
*1:「そんな軟弱な態度じゃマズイだろう」という理由はウソだけど、「ループ用のマクロが多用される」という認識自体は正しいと思う。