2016-12-23-01-CL-OVERLOAD:コンストラクタのためのコンパイラマクロ定義機能を追加 - project-enigma

2016-12-23-01-CL-OVERLOAD:コンストラクタのためのコンパイラマクロ定義機能を追加

>> Site top >> weblog >> 月別アーカイブ >> 2016年12月のlog >> 2016-12-23-01-CL-OVERLOAD:コンストラクタのためのコンパイラマクロ定義機能を追加

最終更新日付:2016/12/25 22:17:27


CL-OVERLOAD:コンストラクタのためのコンパイラマクロ定義機能を追加

2016 年 12 月 23 日

えーと、どこから説明しようかな。stl:make_tupleコンパイラマクロが追加された結果、(new stl:tuple ...) とやるのが嫌になってきた。だから CL-OVERLOAD を改造しようと思って試したら上手くいった、という話。

まずはおさらいから。stl:make_tuple は関数だがコンパイラマクロも用意されている。結果、以下のように展開される。

(stl:make_tuple a b c)

=> (LOCALLY (DECLARE (OPTIMIZE SPEED))
    (LET ((#:ARR546 (MAKE-ARRAY 3 :INITIAL-ELEMENT NIL)))
      (DECLARE (TYPE VECTOR #:ARR546))
      (_= (AREF #:ARR546 0) A)
      (_= (AREF #:ARR546 1) B)
      (_= (AREF #:ARR546 2) C)
      (MAKE-INSTANCE 'CL-STL:TUPLE :ITEMS #:ARR546)))

 

一方、現状では (new stl:tuple a b c) は以下のようになる。

(new stl:tuple a b c)
  => (CL-STL::__NEW-TUPLE A B C)
  => (CL-STL::__NEW-TUPLE-0+ A B C)

 

‥‥‥ここで、そもそも CL-OVERLOAD の new はマクロだ。こいつは規約に従ってコンストラクタ関数名を生成し、その呼び出しに置き換える。上記では CL-STL::__NEW-TUPLE がそれだ。これは関数であり、実行時にコンストラクタオーバーロードの宣言に従って(パラメータ数を元に)オーバーロード名の解決をし、適切なオーバーロード関数にディスパッチする。しかし、パラメータ数のみによる解決ならばそれはコンパイル時点でもできるよね、というわけで、CL-STL::__NEW-TUPLE はコンパイラマクロも用意されている。だから、上記のマクロ展開は CL-STL::__NEW-TUPLE-0+ の呼び出しにまで到達するわけだ。

しかし、オーバーロードの「パラメータ数による解決」はコンパイル時点でできても、「型による解決」の部分はコンパイル時点ではできない。だからコンストラクタオーバーロードの一番奥底は総称関数になっているわけだ。ここでは、CL-STL::__NEW-TUPLE-0+ がそれだ。

でも、stl:make_tuple に対応するようなパラメータの型にも数にも頓着しないようなオーバーロードでは、総称関数としてのメリットはない。パラメータは &rest 引数しかないからだ。

そうなると、これもなんとかしてコンパイラマクロで展開できないかと思ってしまう。その最大の理由は、コンテナの emplace 系メソッドだ。CL-STL では emplace をする度に型を指定せざるをえないため、呼び出しは以下のようになり、そして以下のように展開される。

(stl:emplace_back vec stl:tuple (a b c))
  => (CL-STL::__EMPLACE_BACK-2 VEC (NEW CL-STL:TUPLE A B C))
  => (CL-STL::__EMPLACE_BACK-2 VEC (CL-STL::__NEW-TUPLE A B C))
  => (CL-STL::__EMPLACE_BACK-2 VEC (CL-STL::__NEW-TUPLE-0+ A B C))

 

結果、insertpush_back とは違ってオブジェクトが内部でコピーされることはなくなる(そもそもこれが emplace 系のメリットだ)ものの、new からの展開における総称関数呼び出しを避けることはできず、&rest 引数によるリストを実行時にこねくり回すことも避けられないのだ。嗚呼。

そんなわけで、上記の例で言うならば CL-STL::__NEW-TUPLE-0+ 、これをコンパイラマクロにしたくなる。しかし、そんなことができるのだろうか? だって関数じゃなくて総称関数だよ? と思ったものの、試したり調べたりしないで諦めるのは良くないよね。そう思って試してみたところ、なんとできました。いくつか試してみて、以下のことがわかった。

最初の点については、「コンパイラマクロにできるならそもそも総称関数にする意味がないだろ」と言われるかもしれない。まぁ、&rest 引数だけだからね。これについては言い訳しておくと、今問題にしているのが CL-OVERLOAD のコンストラクタオーバーロードの仕組みの中の話だからだ。先にも書いたことだけれど、コンストラクタオーバーロードの一番奥底では、全てを総称関数にすることになっている。だからここはひとまず避けられない(とはいえ、パラメータ数がゼロの場合と &rest 引数だけの場合は強制的に総称関数でなく関数にする、ということはできるかもしれない)。

問題は2番目だ。戯れに試してみたところ、define-compiler-macro しかしていないものでも、とりあえずは動作するようなのだ。もちろん、そのような名前に対して #'foo とかすることはできなかったけれど。

で、そうなると、当然次の疑問が出てくる。とりあえず HyperSpec を見てみたけれど、自分には答えを見つけ出すことができなかった。

ちょっと話が逸れ過ぎてしまったようだ。上記のような疑問はあるものの、CL-OVERLOAD に define-constructor-macro を追加。そして CL-STL の tuple クラスのコンストラクタでこれを利用するようにした。まだ github にはアゲていないけれど、この連休でやるつもり。

 

コメント

  - 12/25/2016 21:15:56

コンパイラマクロについて twitter に書いたら、以下の指摘を頂いた。

あわせて HyperSpec の記載箇所も指摘して頂きました。感謝致します。

あと、ついでに。この記事で書いている修正は GitHub にも push しました。

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

 

このページのタグ

Page tag : STLとその移植

Page tag : Common Lisp

 

 


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