id:eel3:20090125:1232892677 で自作iotaをCommon Lispで書き直したまま放置状態だったのを思い出したので、勉強を兼ねて修正することにした。
まずドキュメント文字列を追加することにした。中身はインチキ英語。SRFIは仕様というかドキュメントなので、「SRFI-1から移植した」という表現はおかしく感じるのだけど*1、私の力量では良い修正案が思いつかない。
; 再帰版 (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (if (<= count 0) '() (cons start (iota (- count 1) (+ start step) step)))) ; 末尾再帰(反復)版 (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (labels ((iota-iter (count num list) (if (<= count 0) (reverse list) (iota-iter (- count 1) (+ num step) (cons num list))))) (iota-iter count start '()))) ; 末尾再帰(反復)版の trace対応バージョン ; (trace iota-iter) してからiotaを呼べばOK (defun iota-iter (count start step list) (if (<= count 0) (reverse list) (iota-iter (- count 1) (+ start step) step (cons start list)))) (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (iota-iter count start step '()))
次に破壊的にリストを操作する関数を使ってみることにした。consとreverseは非破壊的な関数なので毎回新たなリストが生成されるのだけど、iotaの実装としてはconsやreverseで操作した結果は必要だけど操作前のリストは不要なので、破壊的に操作してしまっても問題ない。
破壊的な関数に変更した方が少しは経済的になるだろうし、ガベージコレクションの心配も減るだろう。ということでconsをpushに、reverseをnreverseに変更してみた。
; 再帰版 (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (if (<= count 0) '() (let (nums) (setf nums (iota (- count 1) (+ start step) step)) (push start nums)))) ; 末尾再帰(反復)版 (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (labels ((iota-iter (count num list) (if (<= count 0) (nreverse list) (iota-iter (- count 1) (+ num step) (push num list))))) (iota-iter count start '()))) ; 末尾再帰(反復)版の trace対応バージョン ; (trace iota-iter) してからiotaを呼べばOK (defun iota-iter (count start step list) (if (<= count 0) (nreverse list) (iota-iter (- count 1) (+ start step) step (push start list)))) (defun iota (count &optional (start 0) (step 1)) "iota is a function ported from Scheme SRFI-1." (iota-iter count start step '()))
再帰版は少し工夫が必要。単純に以前のコードのconsをpushに変更しただけでは評価に失敗してしまい、動かない。pushはマクロなのだけど、どうもマクロ展開時に評価に失敗するようなコードが生成されてしまうらしい*2。今回は一時的にレキシカル変数を挟み込むことで回避した。