等価

等価性 の Common Lisp

1個の演習

等価性について

Common Lispにも、他の言語と同様に、2つのオブジェクトが「同じ」かどうかを判断するためのルールがあります。 このルールは4つのレベルを定義していて、それぞれのレベルには、そのレベルでのチェックを行う関数があります。 レベルは、最も厳しいものから最も緩いものへと並んでいます。

eq

最初のレベルは、オブジェクトの同一性です。 この等価性は、関数eqでチェックします。 等しいかどうかを調べる2つのオブジェクトは、まったく同じオブジェクトでなければなりません。

(eq 'apples 'apples)  ; => T
(eq 'apples 'oranges) ; => NIL

(eq '(a b c) '(a b c) ; => NIL (these two lists have the same contents but are not the same list)
(let ((list1 '(a b c)) (list2 list1)) 
  (eq list1 list2))   ; => T (these two lists are the same list)

eql

2番目のレベルでは、数値と文字の等価性が加わります。 この等価性は、関数eqlでチェックします。 チェックの方法は、引数の型によって変わります。

  • eqである2つのオブジェクトは、eqlです
  • 数値は、型と値が同じであればeqlです
  • 文字は、同じ文字を表していればeqlです
(eql 1 1)     ; => T
(eql 1 1/1)   ; => NIL (one number is an integer, the other a rational)
(eql #\c #\c) ; => T
(eql #\c #\C) ; => NIL (case is different)

なぜ数値と文字が、eqによるオブジェクトの同一性で比較されないのか、不思議に思うかもしれません。 Common Lispの標準では、実装が望むなら数値や文字をコピーすることが許されています。 そのため、0と0はeqでないことがあります。それぞれが数値0の異なるインスタンスである可能性があるからです。

equal

3番目のレベルでは、構造的な類似性をチェックします。 この等価性は、equalでチェックします。 チェックの方法は、引数の型によって変わります。

  • シンボルは、eqと同様に比較されます
  • 文字と数値は、eqlと同様に比較されます
  • コンスは、その要素がequalであればequalです。 これは再帰的に行われます。
  • 文字列とビットベクターは、その要素がeqlであればequalです
  • その他の型の配列は、eqと同様に比較されます
  • パス名は、機能的に等価であればequalです。 (ここには、パス名の構成要素となる文字列の大文字小文字の扱いに関して、実装依存の動作が入り込む余地があります。)
  • その他の型のオブジェクトは、eqと同様に比較されます
(equal '(a (b c)) '(a (b c)))         ; => T (conses are equal if their contents are equal)
(equal "hello" "hello")               ; => T
(equal "hello" "HELLO")               ; => NIL
(equal #(1 2 3) #(1 2 3))             ; => NIL (arrays are equal only if eq)
(equal #P"foo/bar.md" #P"foo/bar.md") ; => T (pathnames are equal if "functionally equivalent"

equalp

4番目で最も緩いレベルの等価性は、equalpでチェックします。 チェックの方法は、型によって変わります。

  • 2つのオブジェクトがequalpであれば、それらはequalpです
  • 数値は、型が同じでなくても値が同じであればequalpです
  • 文字と文字列は、大文字小文字を区別せずに比較されます
  • コンスは、その要素がequalpであればequalpです。 これは再帰的に行われます。
  • 配列は、次元数が同じで、それらの次元が一致し、各要素がequalpであればequalpです。
  • 構造体は、同じクラスとスロットを持ち、それらのスロットのそれぞれが2つの構造体の間でequalpであればequalpです。
  • ハッシュテーブルは、両方が同じ:test関数を持ち、同じキーを持ち(その:test関数で比較した場合)、それらのキーがequalpで比較して同じ値を持てばequalpです。
(equalp 1 1.0)                       ; => T
(equalp #\c #\C)                     ; => T
(equalp "hello" "HELLO")             ; => T
(equalp #(1 2 3) #(1.0 2.0 3.0))     ; => T (arrays contain elements which are `equalp`)
(equal #S(TEST :SLOT1 'a :SLOT2 'b) 
       #S(TEST :SLOT1 'a :SLOT2 'b)) ; => T (structures of the same class with slots that have values which are `equalp`)

型に特化した関数

ここまでに挙げたのは、「汎用的な」等価性の関数です。 これらは、定義どおり、どのような型に対しても機能します。 これは、比較するオブジェクトの型が実行時までわからないような汎用的なコードを書くときに役立ちます。 ただし、比較する型がわかっている場合には、型に特化した等価性の関数を使うほうが「よりよいスタイル」だと一般に考えられています。 たとえば、equalではなくstring=を使います。 これらの関数は、関連するコンセプトの中で紹介し、説明します。

GitHubで編集 リンクは新しいウィンドウまたはタブで開きます

等価性を学習する