#:g1: QITAB: strict-functionsの紹介

Posted 2014-11-16 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の321日目です。

QITAB: strict-functionsとはなにか

 QITAB: strict-functionsは、ITAで利用されている引数や返り値の型、発生するコンディションのチェック機能付きの関数/メソッドを定義するためのライブラリです。

パッケージ情報

パッケージ名QITAB: strict-functions
Quicklisp×
プロジェクトサイトQITAB - a collection of free Lisp code

インストール方法

 common-lisp.netからITAで利用されているユーティリティのスナップショットが入手できるので、これをダウンロードします。

目的のファイルは、quux/lisp/quux/strict-functions.lisp あたりのファイルです(結構色々なファイルに散らばっています。)

試してみる

 関数のドキュメンテーションが詳細なので掲載してみます。

DEFINE-STRICT-FUNCTION names a macro:
  Lambda-list: (NAME
                (&KEY INPUTS (OUTPUTS NIL ANY-OUTPUTS) CONDITIONS
                 COUNT-P)
                &BODY BODY)
  Documentation:
    Define a function (like defun) with strict input, output and signal typing.

'name' - symbol naming the function

'inputs' - a "strict lambda list". Similar to a regular defun lambda-list, however every atom/expression PARAM which describes a parameter value is now a list (PARAM TYPE &optional DOC), where TYPE is a type expression which constrains the type of that parameter and DOC is an optional DOCSTRING describing the parameter. For &rest arguments, TYPE is a type expression that constrains the type of all following parameters.

'outputs' - a list of type expressions which constrain the types of values returned by the function, one type per value, as would be seen by the caller. The function is not allowed to return more values those described by this list. If there is no outputs argument, then any returned values are OK; use this when the function is called only for side effect and the returned value should be ignored.

'conditions' - a list of the conditions which this function is allowed to signal. If 't' is in this list, any condition is allowed.

'count-p' - true or nil: if true, result counts are maintained. Specifically, when function exits with a condition of type not listed in :CONDITIONS argument it is counted as an error, and otherwise as an success. This is for reporting by the /stat/request monitor facility, intended to be used by operators. By convention, count-p is specified true for QRes functional entry points, i.e., by define-qres-functional-entry-point, otherwise nil.

'body' - the body of the function (as in DEFUN, the first form may be a documentation string).

Type declarations are automatically prepended to the function body for each of the input parameters. Violations of input type declarations are signalled as a STRICT-FUNCTION-INPUT-TYPE-ERROR via #'ERROR.

If the function attempts to return a more values than declared by :outputs, then a STRICT-FUNCTION-OUTPUT-COUNT-ERROR is signalled via #'ERROR. Likewise, If the function attempts to return a value with a type incompatible with that declared by :outputs, then a STRICT-FUNCTION-OUTPUT-TYPE-ERROR is signalled via #'ERROR.

The strict-function wrapping will catch any signals which are not declared by :conditions and signal the STRICT-FUNCTION-CONDITION-ERROR via #'ERROR if it matches *strict-function-condition-signal-typespec* (which defaults to NIL).

Example:

(define-strict-function foo (:inputs ((x keyword) &optional ((y 57) integer)) :outputs (integer string) :conditions (bad-foo-error)) (case x (:ANIMAL (values y "elephants")) (:MINERAL (values (* 2 y) "rocks")) (t (error (make-condition 'bad-foo-error)))))

(define-strict-function bar (:inputs ((x t)) :outputs (string) :conditions ()) (format nil "bar sez: ~A" x))

 オプションのうちcount-pというのがQResシステムに密着気味ですが、他は汎用的かなと思います。
とりあえず、define-strict-functionの方は、

(define-strict-function fib (:inputs ((n (integer 0 *)))
                             :outputs (integer))
  (if (< n 2)
      n
      (+ (fib (1- n))
         (fib (- n 2)))))
(fib :z)
;!> The value of argument N to FIB was :Z but it was expected to be of type (UNSIGNED-BYTE
;!>                                                                          62)
;!>    [Condition of type STRICT-FUNCTION-INPUT-TYPE-ERROR] 

という感じです。
コンディションを指定する場合は、

(setq *strict-function-condition-signal-typespec* T)

(define-condition morlis () ())

(define-strict-function latumapic (:inputs () :outputs (null) :conditions (morlis)) (warn "大丈夫か日本"))

(latumapic) ;!> The condition #1# was erroneously signalled in LATUMAPIC: ;!> #1=大丈夫か日本 ;!> [Condition of type STRICT-FUNCTION-CONDITION-ERROR]

という感じで、指定したコンディション以外が発生するとSTRICT-FUNCTION-CONDITION-ERRORになります。

 define-strict-methodの方は、


(define-strict-generic fib (:inputs ((n integer))
                           :outputs ((integer 0 *))))

(define-strict-method fib (:inputs ((n (eql 0))) :outputs ((integer 0 *))) 0)

(define-strict-method fib (:inputs ((n (eql 1))) :outputs ((integer 0 *))) 1)

(define-strict-method fib (:inputs ((n integer)) :outputs ((integer 0 *))) (+ (fib (1- n)) (fib (- n 2))))

(fib -1) ;!> The condition #1# was erroneously signalled in FIB: ;!> #1=Control stack exhausted (no more space for function call frames). ;!> This is probably due to heavily nested or infinitely recursive function ;!> calls, or a tail call that SBCL cannot or has not optimized away. ;!> ;!> PROCEED WITH CAUTION. ;!> [Condition of type STRICT-FUNCTION-CONDITION-ERROR]

という感じです。
ちょっと判りづらいですが、メソッドの方は、スタックが溢れてエラーになっているので、STRICT-FUNCTION-CONDITION-ERRORになっています。

まとめ

 今回は、QITAB: strict-functionsを紹介してみました。
QITAB: strict-functionsのことを初めて識ったのは、 Steve Yegge氏のブログエントリーにDan Weinreb氏が寄せたコメントでした(ちなみにブログのコメントにしては恐しく長文)。

どんなものか適当に想像して作ってみたりしたこともありました(#:g1: CLでのDylan風定義とCLOS系言語での型指定書法の比較)が、実物はかなりゴツい感じですね。

comments powered by Disqus