Posted 2019-08-11 20:29:48 GMT
思えば、Common Lispでアリティ(引数の個数)の不整合でエラーを喰らうのは実行時が多い気がしますが、ANSI Common Lisp規格ではコンパイル時に弾くことってできないんでしょうか。
……と思ってHyperSpecにあたってみましたが、コンパイル時にアリティの不整合を検知した場合には、エラーにしてしまうコンパイラがあっても良さそうではありました。
Common Lispは動的言語なので、実行時でも関数等の再定義が可能ですが、上記の3.2.2.3 Semantic Constraints
を眺める限りでは、どちらかといえば、コンパイラ動作の方に重きを置いているように見え、再定義されなくても良い場合を幾つか挙げています。
逆に再定義が保証されることを主として考えるとすると、少なくともnotinline
宣言がついてない場合の再定義は、関数が実行時に確実に置き換わることは期待できなさそうです。
また、コンパイラ/インタプリタ動作の整合性とは別に、アリティのチェックについても記載がありますが、エラーは、実行時もしくはコンパイル時に挙げるとあります。
上記は、関数呼び出し時のエラーについての規定なので、基本的に実行時エラーだけで良さそうにも思えますが、マクロ展開等も考慮するとコンパイル時も含めないといけないのでしょうか。
ともあれ、safe callの観点からもコンパイル時に検出することも可能ではありそうです。
ちなみに、組み込み関数はsafe callの観点からすると、アリティの不整合はコンパイル時にエラーにできそうです。
呼出しのアリティに不整合があった場合にコンパイルエラーになっても良さそうではあるのですが、実際の処理系の挙動を確認してみました。
このようなファイルをコンパイルして、
(defun foo (x y)
(list x y))(defun bar (x y)
(foo x))
下記のように実行してみます。
(bar 1 2)
>>> invalid number of arguments: 1
処理系のメジャーなところは一通り確認してみましたが、コンパイルエラーにする処理系はECLのみのようで、他は警告は出すもののコンパイルは通し、faslをload
して実行したら当然実行時エラーです。
まあ大体Common Lispってこんな感じの動作でしたね、というところ。
むしろECLの動作のほうが意外かもしれません。
処理系 | compile-file |
---|---|
SBCL | 警告あり |
CLISP | 警告あり |
LispWorks | 警告あり |
Allegro CL | 警告あり |
Clozure CL | 警告あり |
ECL | コンパイルエラー |
MkCL | 警告なし |
Clasp | 警告なし |
CMUCL | 警告あり |
さて、規格上はコンパイル時にアリティの不整合を検出してエラーにしても良さそうだけれど、実際のところデフォルトでそういう挙動をする処理系は、殆ど存在していないことが分かりました。
コンパイル時でもなんでもできるCommon Lispなので、工夫はあれこれできそうですが、とりあえずコンパイラマクロを試してみましょう。
とりあえずは、何もしないコンパイラマクロを定義するのみですが、展開時に引数チェックでエラーになることが期待できます。
(defun foo (x y)
(list x y))(define-compiler-macro foo (x y)
`(foo ,x ,y))
(defun bar (x y)
(foo x))
処理系 | compile-file |
---|---|
SBCL | 警告あり |
CLISP | コンパイルエラー |
LispWorks | コンパイルエラー |
Allegro CL | 警告あり |
Clozure CL | コンパイルエラー |
ECL | コンパイルエラー |
MkCL | 警告なし |
Clasp | コンパイルエラー |
CMUCL | 警告あり |
軒並エラーになると予想していましたが、コンパイルが通ってしまうものもある様子。
これは、積極的にエラーにする他ないのかもしれません。
ということで下記のように書いてみました。
(eval-when (:compile-toplevel :load-toplevel :execute)
(define-condition argument-mismatch (program-error)
((form :initarg :form :reader argument-mismatch-form)
(text :initarg :text :reader argument-mismatch-text))
(:report (lambda (c s)
(format s
"~A:~%~S~%"
(argument-mismatch-text c)
(argument-mismatch-form c))))))(defun foo (x y)
(list x y))
(define-compiler-macro foo (&whole w &rest args)
(etypecase (length args)
((eql 2) w)
((integer * 1)
(error 'argument-mismatch :text "Too Few Arguments" :form w))
((integer 3 *)
(error 'argument-mismatch :text "Too Many Arguments" :form w))))
(defun bar (x y)
(foo x))
しかし、SBCL、Allegro CLはコンパイルを通す様子。
処理系 | compile-file |
---|---|
SBCL | 警告あり |
CLISP | コンパイルエラー |
LispWorks | コンパイルエラー |
Allegro CL | 警告あり |
Clozure CL | コンパイルエラー |
ECL | コンパイルエラー |
MkCL | 警告なし |
Clasp | コンパイルエラー |
CMUCL | 警告あり |
SBCLでは、トラップしたいなら、*break-on-signals*
を設定しろと警告が出るので、SBCL、Allegro CL、CMUCLはそういうポリシーなのでしょう。
; caught WARNING:
; Error during compiler-macroexpansion of (FOO X). Use *BREAK-ON-SIGNALS* to
; intercept.
;
; Too Few Arguments:
; (FOO X)
Common Lispでアリティの不整合をコンパイルエラーにする方法を探ってみましたが、
*break-on-signals*
に適宜設定の組み合わせで大抵の処理系では実現できそうです。
ちなみに、型の不整合もチェックしたいところですが、SBCLであれば、今回のコンパイラマクロの手法を応用して、deftransform
あたりを使えばできなくもなさそう。
■
HTML generated by 3bmd in LispWorks 7.0.0