nptclのブログ

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

eqlには3種類の意味がある

表題の通り、Common Lispでは下記の意味があります。

  • 関数eql
  • eql
  • eql-specializer

なんでこんなにあるんでしょうね。
兄弟分である、eqequalequalpさんたちは1つの意味しかありませんよ?

それぞれ説明していきます。

関数eql

普通はこいつです。
2つのオブジェクトが等しいかどうかを調べるときに使います。

関数eqより適当ではないものの、関数equalみたいに 詳しく調査して欲しくないという位置づけだと思います。

厳密に定義されており、下記のどれかが当てはまっているならtです。

  1. eqtの場合。
  2. どちらもnumberであり、同じ型で同じ値の場合
  3. どちらもcharacterであり、同じ文字の場合

1つめのeqはそのままアドレスを比較します。
2つめは、同じ型が要求されているので、数値として等しいだけの場合はダメ。

* (eql 0.0 0)nil

* (eql 0.0 0.0)t

* (eql 0.0d0 0.0s0)nil

* (eql 0.0d0 0.0d0)t

3つめはそのままです。

* (eql #\a #\a)t

* (eql #\a #\A)nil

eql

Type-specifierと呼ばれるものであり、 型という中で、値がeqlかどうかを調査します。

例えば次の通り。

* (typep 10 '(eql 10))t

* (typep (cons nil nil) `(eql ,(cons nil nil)))nil

余談ですが、型を評価する際に、内部で別の形式に変形することが良くあります。
例えば

(or (integer 10 200)
    (integer 100 300))

(integer 10 300)

のように変えたりします。

eqlの場合も

(eql 10)

(integer 10 10)

に変形することがあります。
だから何だってわけじゃないんですが。

eql-specializer

これはmethodを定義するときに使うものです。

methodは、引数に値の特性を指定することができます。
例えば下記の通り。

(defmethod aaa ((a integer) (b string))
  ...)

この場合は、第一引数aintegerを、第二引数bstringを指定しています。
注意して欲しいのは、このintegerstringと記述しているのは、 「型」ではなく「classおよびeql-specializer」です。

下記の例の引数は、型(eql 10)ではなく、eql-specializerの指定です。

(defmethod bbb ((a (eql 10)))
  ...)

何が違うんだとしか思えないでしょう。
integerと書けて、(eql 10)と書けるなら、型じゃないのか?
でも型じゃないんです。

型が指定できないので

(defmethod bbb ((a (satisfies oddp)))
  ...)

(defmethod bbb ((a (not (eql 10))))
  ...)

はエラーです。
こいつもエラーなので注意。

(defmethod bbb ((a (real 10 20)))
  ...)

じゃあintegerstringは型じゃないのか? と思われるかもしれませんが、型じゃありません。
型のふりをしたclosオブジェクトです。

closの世界とlispの世界をある程度似せるために、 symbolだけで表記された標準の型については、 同じclassが用意されている場合があります。

何が用意されているのかは処理系依存ですが、 find-class関数を使うことで調査できます。

clispの例を示します。

(find-class 'integer nil)
  →#<BUILT-IN-CLASS INTEGER>

(find-class 'atom nil)nil

なお、eql-specializerの引数は、 defmethod宣言時に一度だけ評価されます。
methodが呼び出されるたびに評価されるわけではありません。

例を示します。
呼び出すたびに値が増えていく関数helloを作成します。

(let ((value 0))
  (defun hello ()
    (incf value)))

generic-functionaaaを作成します。

(defgeneric aaa (arg))

関数helloeql-specializerの引数として、methodを定義します。

;; ここで(hello)は1を返却
(defmethod aaa ((arg (eql (hello))))
  :hello)
→#<STANDARD-METHOD ((EQL 1))>

実行してみます。

;; eql-specializerが機能している
(aaa 1)
 →:HELLO

;; 何度か実行する
(aaa 1)
 →:HELLO
(aaa 1)
 →:HELLO

;; 確認
(hello)2

;; エラー
(aaa 2)
 →no-applicable-method