2014-04-17-01-演算子オーバーロードについても考える - 3 - project-enigma

2014-04-17-01-演算子オーバーロードについても考える - 3

>> Site top >> weblog >> 月別アーカイブ >> 2014年04月のlog >> 2014-04-17-01-演算子オーバーロードについても考える - 3

最終更新日付:2014/04/17 23:50:00


演算子オーバーロードについても考える - 3

2014 年 04 月 17 日

以前の続き。まだ頭はちゃんと冷えてはいないが、忘れるのも嫌なので。自分は、前回の最後で以下のように書いた。

‥‥‥(略)‥‥‥そしてこのような場合、設定すべき場所にあるモノと、設定すべき値はまったく別のタイプかもしれないのだ。STL では静的な型チェックがあるが、Common Lisp 上で動作する CL-STL にはそんなものはない。だから、前述の operator= を defmethod する場合、同じタイプ同士で特定化するだけではいけない。たとえばコンテナの場合、同じタイプ同士なら「内容のコピー」をし、それ以外ならば別のことをしなければならないだろう。コピー先が nil だったらコンテナの複製を作ることになるかもしれないし、それ以外であれば例外を投げることになるかもしれない。

とりあえずのところ、上記のことは置いておこう。それよりも先に、本気で setf とは別の「代入」を導入したらどうなるのか、それを探っていきたい。

 

さて、今回も色々と都合のいい replace を使っていこう。現状、おおむね以下のようになっている。

(defmethod replace ((first forward-iterator)
                    (last  forward-iterator)
                    old-val new-val &optional (eql-bf #'eql))
  (if (iter= first last)
      nil
      (do ((eql-bf (clone eql-bf))
           (itr (clone first)))
          ((iter= itr last) nil)
        (when (functor-invoke eql-bf old-val (iter* itr))
          (setf (iter* itr) new-val))
        (++iter itr)))))

 

代入の話とは直接関係しないが、「等値比較」のための総称関数 opr== が導入されたらどうなるか、先にそれを見てみよう。こうなる。

(defmethod replace ((first forward-iterator)
                    (last  forward-iterator) old-val new-val)
  (if (iter= first last)
      nil
      (do ((itr (clone first)))
          ((iter= itr last) nil)
        (when (opr== old-val (iter* itr))
          (setf (iter* itr) new-val))
        (++iter itr)))))

 

では、代入はどこに入るのだろうか? 前回示した opr= は、setf 可能な「場所」であればなんでも第一パラメータに取ることができた。だから、上記のコードであれば(iter* itr) に setf しているコード、これを置き換えることができる。ついでだから iter* も opr* に変えておこう。以下のようになる。

(defmethod replace ((first forward-iterator)
                    (last  forward-iterator) old-val new-val)
  (if (iter= first last)
      nil
      (do ((itr (clone first)))
          ((iter= itr last) nil)
        (when (opr== old-val (opr* itr))
          (opr= (opr* itr) new-val))
        (++iter itr)))))

 

さぁ、ここに至ってマズいことに気付いたようだ。上記の opr= のマクロ呼び出し、これの展開結果はいったいどうなっているだろうか。こうなっている。

(let* ((#:itr861 itr))
  (let ((#:new860 (operator= (opr* #:itr861) new-val)))
    (funcall #'(setf opr*) #:new860 #:itr861)
    #:new860))

 

上記の replace では、これは問題ない。アルゴリズムを適用する対象のシーケンスとして受け取るのが forward-iterator だからだ。しかし、「代入先」が出力反復子(output-iterator)であるようなアルゴリズムの場合、上記の opr= 展開コードは動作しない。なぜなら、件の operator= では、その呼び出しによっていったん「現在の反復子が指している場所の値を取得」する必要がある。しかし、値の設定は許しても取得は許さない出力反復子の場合、これができないのだ。言い換えれば、出力反復子の場合、(setf opr*) は実装しても opr* は実装しないためにそうなってしまうのだ。

出力反復子の場合に、setf でない opr* が常に nil を返す「ことにする」、それによって、この問題は回避できる。本物の output-iterator(つまり前方向でも双方向でもランダムアクセスでもない、『本物』の出力反復子)であれば、「何もないところに書き込んでいく」ものだからだ。しかし、本当にそんなに楽観的になって良いものだろうか。現に、自分はこのページを書きながらようやくこの問題に気付いたではないか。今後、また別の問題に行き当たらないとも限らない。

‥‥‥もう少し、考え続けよう。やれやれ。

 

コメント

このページにコメントする

 

このページのタグ

Page tag : Common Lisp

Page tag : STLとその移植

 

 


Copyright(C) 2005-2017 project-enigma.
Generated by CL-PREFAB.