🦉

FTYPE declaration for generic functions. (Common Lisp)

2021/09/20に公開

TLDR

  1. ある程度最適化したい。
  2. いちいちoptimize宣言を各関数に挟みたくない。
  3. ファイルの先頭でグローバルにoptimize宣言をdeclaimで行う。
  4. SBCLがコンパイラノートをめっちゃ吐く。
  5. ノートに従うことによりノートを消したい。
  6. 総称関数の返り値わからん問題によりノートが消せない。
  7. FTYPE宣言により消せましたよ。

というお話。

CLOS

CLOSは限界まで動的にしようとしているきらいがあります。
すなわち「実行時に」クラスが書き換えられることをも視野に入れていることを意味します。
総称関数もCLOSの一部なためその文脈に従います。
すなわち「実行時に」メソッドが書き換えられることをも視野に入れているということです。

ところが実際に実行時に振る舞いを書き換えたい場合がどれくらいあるかと申しますと、あまりありません。
総称関数を採用する多くの場合、欲しいのは引数の型による振る舞いのディスパッチでしょう。
振る舞いそのものを実行時に変更したいという需要はそう多くはありますまい。

たとえばあるクラスを宣言するとします。
以下のようなものです。

(defclass c ()
  ((s :type fixnum :reader s)))

クラスCはスロットSを持ち、そのスロットはリーダメソッドSで読み取ります。

さて、問題はリーダSの返り値です。

スロットSではFIXNUMであると型宣言されています。
Sの返り値は必ずFINXUMになりそうなものです。
実際そのように振る舞うのですが、コンパイラはこの推測を行えません。
というのもクラスや総称関数は実行時に書き換えられるかもしれないからです。

以下のフォームは最適化できません。

(1+ (s var))

Sの返り値は実行してみない限り分からないからです。

CLOSの思想としてそのような振る舞いを取るのに否やはありません。
されど一般的な需要としてはやはり動的に振る舞いを変えたい場合は少のうございます。

これをなんとかしたい。
どのようなメソッドが追加されたとしても、Sは必ずFIXNUMを返すのだと宣言したい。
しかしCLOSではそれを実現できない。

FTYPE declaration.

FTYPE宣言を行うことでこれを実現できるらしい。

「らしい」という曖昧な表現になってしまっているのは、これが仕様では明言されていないからです。
ただし、FUNCTION-NAMEは関数のサブタイプでなければならない(だからマクロには使えない)という一文があることから、総称関数に対して宣言しても問題ないと解釈できるにはできます。
もっともここで言う「サブタイプ」が想定しているのが総称関数であるというのは少々疑わしく、おそらくはCOMPILED-FUNCTIONを指しているのだろうとは思いますが。

以下のように宣言をするとコンパイラはノートを吐かなくなります。

(defun demo (c)
  (declare (ftype (function (c) (values fixnum &optional)) s))
  (1+ (s c)))

declaimでもってグローバルに宣言しても良いのですが、その場合再読込時にコンパイラが今度は警告を発するようになります。

この場合の警告は「関数の型が宣言されているが同名のものが総称関数として定義されたので宣言を無効にする」というものです。

DECLAREによるローカルな宣言の場合そのような警告は出ません。

Conclusion.

本気で最適化したい場合は総称関数を使わないのがベターだとは思います。

そのような場合、以下のライブラリが助けになるやもしれません。

Discussion