#:g1: CLでのDylan風定義とCLOS系言語での型指定書法の比較

Posted 2008-06-06 10:49:00 GMT

CL界の巨人、Weinreb氏のSteve Yegge氏のブログへのコメントで、DEFINE-STRICT-FUNCTIONというものを社内で定義して使っている、というのを目にしました。
-(http://steve-yegge.blogspot.com/2008/05/dynamic-languages-strike-back.html?showComment=1210600080000#c4154142829624462932)
詳細は不明なのですが、型指定の記述を助けるようなマクロのようです。
型の指定といえば、CLでも可能ですが、上述のDEFINE-STRICT-FUNCTIONは、Dylanや、それに影響を受けているGOO風な書式に違いないと、勝手に決めこみ、Dylan風の関数定義マクロを作って使い勝手を探ってみることにしました。

どんな関数を定義するか

x、yのinteger型の2引数を取り、返り値は、多値を返し、値はすべてinteger型という指定

Dylanの書法

define function foo
    (x :: <integer>, y :: <integer>)
 => (#rest number :: <integer>)
  values(x, y)
end function foo;

S式Dylanの書法

(define-function foo ((x <integer>) (y <integer>) 
                      #values #rest (number <integer>))
  (values x y))
;; ※注 S式Dylanには、define-methodしかありません。

GOOの書法

;; 多値がないので、タプルを使うことになると思われるが、詳細不明…(^^;
;; こういう指定はできないのかも…。
(df foo (x|<int> y|<int> => (tup ,x|<int> ,@y|<int>))
  (tup x y))
という感じで、CLのDEFMETHODに返り値の型指定がついたような感じです。
DylanとGOOでは、=>以降が返り値の記述で、S式Dylanでは、#values以降が=>に相当します。
ということで、S式Dylan風のものをガチャガチャ作ってみました。

Dylanを真似たパチモノ

(define-function foo ((x <integer>) (y <integer>) 
                      &values &rest (number <integer>))
  (values x y))
これは、
(defun foo (x y)
  (declare (<integer> x) (<integer> y))
  (the (values &rest <integer>) (values x y)))
のように展開されます。
;; Dylan的な雰囲気づくりの準備
(deftype <integer> (&rest args)
  `(integer ,@args))

(deftype <fixnum> (&rest args) `(signed-byte 32 ,@args))

;; マクロ展開例

;; 1 (define-function foo (x y) (values x y 3))

;>>> (defun foo (x y) (the t (values x y 3))) ;; 返り値チェックはtなので全部通過

;; 2 (define-function foo ((x <integer>) (y <integer>) &values &rest (number <integer>)) (values x y 3))

;>>> (defun foo (x y) (declare (<integer> x) (<integer> y)) (the (values &rest <integer>) (values x Y 3)))

;; 3 (define-function foo ((x <integer>) (y <integer>) &values (x <integer>) (y <integer>) (z <integer>)) (values x y 'foo)) ;==> コンパイル時に警告 & 実行時エラー

(define-function foo ((x <integer>) (y <integer>) &values (x <integer>) (y <integer>) &rest (z <integer>)) (values x y 'foo))

;==> コンパイル時に警告 & 実行時エラー

という感じです。

CLでの書法

(defun foo (x y) 
  (declare (integer x y))
  (the (values integer integer &rest integer)
    (values x y 'foo)))
CLでは、DECLAREで引数の型を指定して、THEで返り値をチェックするようです。
ただし、THEのフォームが複雑になるとコンパイラがチェックしきれない、と警告がでることがあるようです。
また、CMUCLや、それの流れのSBCLでは、DECLAREの中でvaluesというものが使え、返り値のチェックに使えるようです。
これは、THEに展開されるものとのこと。
;;CMUCL/SBCL拡張での書法
(defun foo (x y) 
  (declare (integer x y))
  (declare (values integer integer &rest integer))
  (values x y 'foo))

まとめ

Dylan風の方がわかりやすいかと思いましたが、複雑になると、普通のCLの書法の方がすっきりしてしまいます。
でも、3つ以上の多値で型チェックしたいというようなこともあまりない気もするので、Dylan風でも良いかなとは思ったり…。
…あんまり意義のある考察になりませんでした(笑)

おまけ

;; 不完全定義 (optional、key対応忘れ…)

(defpackage :dylan-compat (:use :cl) (:nicknames :dylan))

(in-package :dylan)

(defun spec-vars (spec) (mapcar (lambda (x) (if (consp x) (car x) x)) spec))

(defun spec-decls (spec) (reduce (lambda (x res) (if (consp x) (cons (reverse x) res) res)) spec :initial-value () :from-end 'T))

(defun out-spec-decls (spec) (reduce (lambda (x res) (cons (cond ((consp x) (cadr x)) ((eq '&rest x) '&rest) ('T 'T)) res)) spec :initial-value () :from-end 'T))

(defun in-&-out-spec (spec) (if (member '&values spec) (let ((pos (position '&values spec))) (list (subseq spec 0 pos) (subseq spec (1+ pos)))) (list spec () )))

(defmacro define-function (name (&rest args) &body body) (destructuring-bind (in-spec out-spec) (in-&-out-spec args) (let ((vars (spec-vars in-spec)) (decls (spec-decls in-spec)) (out-spec (and out-spec (out-spec-decls out-spec)))) `(defun ,name ,vars ,@(if decls `((declare ,@decls)) () ) (the ,@(if out-spec `((values ,@out-spec)) (list T)) ,@body)))))

comments powered by Disqus