nptclのブログ

Common Lisp処理系nptの開発メモです。https://github.com/nptcl/npt

オブジェクト関連で思ったこと

規約を翻訳して思ったことは、現状のnptではかなり規約違反があるということです。 :allow-other-keysなんて対応してないですもん。 地道に修正していこうと思います。

他、気になったことを忘れないように記載します。

qualifierはsymbolだけじゃなくて何でも良かった?

自分が知らなかっただけですが、qualifierはnon-listとの記載がありました。 nil以外のsymbolだけが許容されるんだとばかり思っていたんですが、 何でも良かったんですね。

と思っていたんですが、確認してみるとどうもよくわからない。 ひとまずinteger, character, stringを試してみたんですが、 結果は下記の通り。

  • clispinteger, character, stringを全部を受け付ける
  • sbclintegercharacterのみ
  • cclcharacter, stringのみ

一例を下記に示します。

(define-method-combination test ()
  ((code1 (10 20 "Hello"))
   (code2 (30 40 "aaabbb")))
  `(progn
     ,@(mapcar (lambda (m) `(call-method ,m)) code1)
     ,@(mapcar (lambda (m) `(call-method ,m)) code2)))

(defgeneric zzz (a) (:method-combination test))

(defmethod zzz 30 40 "aaabbb" (a)
  (format t "ccc ~A~%" a))

(defmethod zzz 30 40 "aaabbb" ((a integer))
  (format t "bbb ~A~%" a))

(defmethod zzz 10 20 "Hello" (a)
  (format t "aaa ~A~%" a))

(zzz 999)
 →clispは正常に動作
 →sbclはエラー、stringの"Hello", "aaabbb"がダメ
 →cclはエラー、integerの10, 20, 30, 40がダメ

正常パターンの動作結果は下記の通り。

aaa 999
bbb 999
ccc 999

なんなんだこれは。
まあqualifierなんてもんは、 symbolだけを使うのが無難なんじゃないでしょうか。

1つか複数の返却値ってのはおかしい

0個の返却値も含まれるはず。
この文は「7.6.6.2 Standard Method Combination」あたりに出てくるものであり、 methodの返却値がgeneric-functionでどう扱われるかを説明したものです。

自分で訳しておいてケチ付けるのもアレですが、 たぶん翻訳がおかしいんだと思います。 元の文は「value or values」となっており、 それを「1つか複数の返却値」みたいに訳しています。 でも、たぶん複数の返却値もちゃんと考慮するんですよっていう 気遣いなんじゃないかと思うんですよ。

【間違い】defstructがmethod-defining formsに入っているのはおかしいのでは?

【追記】すみません、間違いです。投稿してから気が付きました。 methodの定義は、reader/writerではなく、print-objectを対象にしています。 よってこの章の言ってることは間違いです。

これは規約の間違いなんじゃないかと思います。
「7.6.1 Introduction to Generic Functions」の「Figure 7-1.」では、 メソッドを定義するオペレーターの中にdefstructを入れています。

でもdefstructreader/writerにあたる関数の生成機能はありますが、 ぜんぶ通常の関数を対象にしているため、generic-functionの生成機能はないはず。 だからこそ、defstructが生成する関数は、generic-function特有の effective methodを特定する動作がない分だけちょっと早いはずなんです。 早いといってもクソみたいな差だと思いますが。

でも自分はまだdefstructを作成したことが無いので 勘違いしているかもしれません。

共有スロットはクラス宣言時にはまだ確保されていないかも?

ANSI Common Lisp範囲外の話だと思います。
規約では7.5.1章に「Defining a shared slot immediately creates a slot.」 みたいに書かれています。 共有スロットが宣言されたとき、即座にスロットが作成されるという意味です。 でもdefclassなんかで:allocation:classのスロットを宣言した瞬間では、 まだそのスロットは値の確保などが済んでいないかもしれません。

sbclでは、class-prototype経由で共有スロットにアクセスすると、 まだfinalizeが完了していないということでエラーになってしまいます。 以前説明したforward-referenced-class関係の話ですね。 でもmake-instanceを経由してもらえば全然大丈夫なので、 これを規約違反とみなすのは無理があるとは思います。

標準のmethod-combinationは例文を見た方がわかりやすい

標準のmethod-combinationと言えば、standardか、 orといった短いフォームになりますが、 使い方を覚えようとしていた時代は、何の解説書を見ても いまいち良くわからなかったことを覚えています。

規約には難しそうなことがごちゃごちゃ書かれていますが、 処理系を実装するんじゃない限り define-method-combinationの使い方をちょっとだけ覚えてから マクロdefine-method-combinationの規約に記載されている 例文を見た方がわかりやすいのではないでしょうか。

下記に例文を抜き出します。

;The default method-combination technique
 (define-method-combination standard ()
         ((around (:around))
          (before (:before))
          (primary () :required t)
          (after (:after)))
   (flet ((call-methods (methods)
            (mapcar #'(lambda (method)
                        `(call-method ,method))
                    methods)))
     (let ((form (if (or before after (rest primary))
                     `(multiple-value-prog1
                        (progn ,@(call-methods before)
                               (call-method ,(first primary)
                                            ,(rest primary)))
                        ,@(call-methods (reverse after)))
                     `(call-method ,(first primary)))))
       (if around
           `(call-method ,(first around)
                         (,@(rest around)
                          (make-method ,form)))
           form))))

短いフォームは次の通り。

;The same thing, using the :order and :required keyword options
 (define-method-combination or 
         (&optional (order ':most-specific-first))
         ((around (:around))
          (primary (or) :order order :required t))
   (let ((form (if (rest primary)
                   `(or ,@(mapcar #'(lambda (method)
                                      `(call-method ,method))
                                  primary))
                   `(call-method ,(first primary)))))
     (if around
         `(call-method ,(first around)
                       (,@(rest around)
                        (make-method ,form)))
         form)))

わからないならごめんなさい。