はじめに
今回は全く使わないものを作りました。ベクタの利点であるランダムアクセスを行わないので、役に立たないです。ただLispで何かを実装したいという自分の欲求を満たしただけということをあらかじめ言っておきます。実際dolistのベクタ版をわざわざ用意せず、coerceでベクタをリストに変換し、それをdolistに渡すだけで速度も問題ないと思います。
なお今回は3パターンの実装をしました。
実装
doマクロによる実装
まずはdoマクロを使った実装です。
(defmacro dovector ((var vector &optional result) &body body)
(let ((index (gensym)) (limit (gensym)))
`(do ((,index 0 (1+ ,index))
(,limit (1- (length ,vector))))
((> ,index ,limit) ,result)
(let ((,var (svref ,vector ,index)))
,@body))))
初めは下のように実装する予定でした。
(defmacro dovector ((var vector &optional result) &body body)
(let ((index (gensym)) (limit (gensym)))
`(do* ((,index 0 (1+ ,index))
(,var (svref ,vector ,index) (svref ,vector ,index))
(,limit (1- (length ,vector))))
((> ,index ,limit) ,result)
,@body)))
do*マクロを使っている点でも速度が遅くなると思いますが、それに加えてこのコードはエラーが出ます。しばらくエラーの原因が分からなくて困っていたのですが、varの更新式にエラーの原因があることに気付きました。この部分で配列の範囲外のところにアクセスしているのでエラーが起きたと考えられます。リストのときに、更新式でcdrを使えば大丈夫ということに慣れていたので、なかなか気付くことができませんでした。またnilのcdrはnilという仕様が有用なものだと改めて実感しました。
loopマクロを使った実装
doマクロを使ったものよりかなりシンプルです。
(defmacro dovector ((var vector &optional result) &body body)
`(progn
(loop for ,var across ,vector do ,@body)
,result))
map関数を使った実装
これもかなりシンプルです。
(defmacro dovector ((var vector &optional result) &body body)
`(progn
(map 'vector #'(lambda (,var) ,@body) ,vector)
,result))
速度について
簡単に説明すると、do < loop < map となりました。やはりdoマクロでの実装が他の2つと比べて圧倒的に速く、map関数を使って実装したものは、規模が大きくなると実用するのはまず無理だと思います。
おわりに
実際に使わないものを作る際にも学ぶことは色々ありました。まずリストとベクタはシーケンシャルアクセスに関しては、ほとんど速度に差がないことを理解しました。テキストを見て知るだけでは、記憶に定着しずらく、リストとベクタをどの場面で使うとよいかを、曖昧さを残したまま理解してしまいます。ですが自分で実装し、体感することにより、忘れることがない知識となるように感じます。
またmap関数の引数としてベクタもとれることもすっかり忘れていました。今回学んだので今後忘れることはないと思います。
長くなりましたが、見てくれた方はありがとうございました。
追記
どうにかしてベクタの利点を生かして実用的にしようと思い、改良しました。
改良内容
まず初めに機能を追加したコードを示します。
(defmacro dovector ((var vector &key result (step 1) from-end) &body body)
(let ((index (gensym)) (limit (gensym)))
(if from-end
`(do ((,index (1- (length ,vector)) (- ,index ,step))
(,limit 0))
((< ,index ,limit) ,result)
(let ((,var (svref ,vector ,index)))
,@body))
`(do ((,index 0 (+ ,index ,step))
(,limit (1- (length ,vector))))
((> ,index ,limit) ,result)
(let ((,var (svref ,vector ,index)))
,@body)))))
resultをoptional引数のままにしておくとエラーになるので、keyword引数にしました。
指定した数の分の間隔を空けた要素に処理
ランダムアクセスを生かすにはとびとびの要素に対して、処理を行うのがいいと考えました。それを行うのがkeyword引数のstepです。使い方を下に示します。
(let ((result '()))
(dovector (x #(1 2 3 4) :step 2)
(push x result))
(nreverse result))
;=> (1 3)
末尾から実行
あれば使うかもしれないので、末尾から実行するためのkeyword引数も用意しました。それがfrom-endというkeyword引数です。使い方を下に示します。
(let ((result '()))
(dovector (x #(1 2 3 4) :from-end t)
(push x result))
result)
;=> (1 2 3 4)
今後の予定
処理を開始する位置もkeyword引数を使って指定できるようにしようかと思いましたが、そこまでいくと機能を追加しすぎのような気もするので迷っています。実装しはじめると欲が出てくるので駄目ですね。
ベクタ専用のmap関数は、mapがあるので作成するのはやめようと思っていましたが、stepというkeyword引数を用意したので、作ってもいいような気がします。実装自体は簡単ですぐ終わると思いますが。