#:g1: defconstantのeql問題について別解を考えた

Posted 2021-12-08 19:51:49 GMT

Lisp一人 Advent Calendar 2021 9日目の記事です。

主にSBCLで発生する問題なのですが、ANSI規格を厳格に解釈した結果、defconstanteqlな関係でないオブジェクトを定数として再定義するとエラーになるというのがあります。

これはOKですが、

(progn
  (defconstant foo-constant 42)
  (defconstant foo-constant 42))

これはNG。

(progn
  (defconstant bar-constant "bar")
  (defconstant bar-constant "bar"))
!>> The constant bar-constant is being redefined (from "foo" to "foo")

SBCL対策

この問題の対策として、再定義しないで値を使い回すようなマクロが良く使われたりしています。
例えば、cl-ppcre::defconstantは、

(ppcre::defconstant bar-constant "bar")
===>
(defconstant bar-constant (if (boundp 'bar-constant) (symbol-value 'bar-constant) "bar"))

のようにして問題を回避します。

さらに汎用的なものでは、alexandria:define-constantのように同値テストを指定できるものもあります。

(define-constant bar-constant "bar" :test #'equal)

他にも結構同様の問題に対処するユーティリティが結構あります。

最近考えた別解

これらは、(boundp 'var)でシンボルをチェックするのですが、開き直って、

(defvar .baz-constant. "baz")

(defconstant baz-constant .baz-constant.)

のように書いてしまっても良いのではないかと最近思ったりしています。 defvarは値の唯一性/再定義回避の担保で、defconstantは、定数の定義のみ、と役割分担しているのだ考えれば、そこまで変でもないのではないでしょうか。

ちなみに、.foo.というドットで囲む表記はSymbolics等ではユーザーはいじってはいけない変数名という慣習だったようですが、このような命名規約を定める程度でトラブルも回避できそう(多分)

まとめ

ちなみにSBCL以外の処理系は、そこまで厳しくないので、(defconstant foo "foo")していても特に問題はありません。
とはいえ、SBCLが現在最大派閥であることは確かなので、自作ライブラリを公開する場合などにはSBCL対応を避けて通ることもできません。

defconstanteqlを求めるのはコンパイルコードに即値として埋め込む等の最適化周りとも関係してくるので、基本的にCommon Lispで定数といったらeqlで同値判定可能なオブジェクトと考えた方がすっきりする気がします。

そう考えると、文字列の場合は、

(defconstant ztesche-constant (string '|ztesche|))

のような記述もアリかもしれません。しかし、少し難解かもしれない……。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus