#:g1: Common Lispのテスト述語を宣言で指定する試み

Posted 2021-05-03 03:24:49 GMT

Common Lispのシークエンス関数のテスト述語はデフォルトでeqlですが、文字列の比較が多い最近では、equalがデフォルトの方が使い勝手が良い気がします。

最近のSBCLだとequalを指定しても最適化でeqleqにしたりするようですが、それと似た雰囲気でデフォルトをequalにし、declareでテスト述語の指定をすることにしたらどうかと思い試してみました。

下準備

declarationdefine-declarationでユーザー宣言のtestを宣言します。
testは、(declare (test #'eql))のように使います。

(defpackage "b85d852d-3639-5467-af00-962feb136730"
  (:use common-lisp)
  (:import-from
   #+lispworks harlequin-common-lisp
   #+sbcl sb-cltl2
   declaration-information
   define-declaration)
  (:shadow find))

(in-package "b85d852d-3639-5467-af00-962feb136730")

(declaim (declaration test))

(define-declaration test (dcl env) (declare (ignore env)) (values :declare dcl))

コンパイラマクロでの最適化

デフォルトはゆるくequalで、宣言でより判定が狭いeqleqを指定した場合に、それが使われるようにします。

(defun find (item sequence &key (test #'equal))
  (cl:find item sequence :test test))

(define-compiler-macro find (&whole w item sequence &key (test #'equal) &environment env) `(cl:find ,item ,sequence :test ,(or (car (declaration-information 'test env)) test)))

確認

(defun find-foo  (x)
  (find x '("foo" "bar" "baz")))

(find-foo "foo") → "foo"

(defun find-foo-dcl-eql (x) (declare (test #'eql)) (find x '("foo" "bar" "baz")))

(find-foo-dcl-eql "foo") → nil

(flet ((sequal (x y) (string-equal x y))) (defun find-foo-dcl-string-equal (x) (declare (test #'sequal)) (find x '("foo" "bar" "baz"))))

(find-foo-dcl-string-equal 'foo) → "foo"

まとめ

最適化の筋書だと、意味論は変えずに効率だけ良くなるような記述体系になる必要がありますが、今回の例だと、任意の述語を指定できるので、意味論が変ってしまうような指定もできてしまいます。

また、define-declarationdeclaimのようなコンパイル単位ローカルなトップレベルの宣言ができれば、ファイルごとに述語のデフォルトを指定できたりして便利なのですが、どうもSBCLもLispWorksもトップレベルの宣言が定義できない様子。
define-declarationは標準機能ではないため、そういう仕様なのか単に実装の都合なのかは微妙ですが、ちょっと不思議。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus