Clojureにおける同一性について

はじめに

Clojureの勉強のため、テスト駆動開発Clojureで写経しはじめました。
第1部は、他国通貨を扱った問題をTDDで順番に解決していくさまが紹介されます。

使用されている言語はjavaであり、オブジェクト指向でTDDが行われます。
一方Clojure関数型言語の仲間であり、オブジェクト指向でいう継承・メソッドといったものがないため、 実現しかたに考えることがあります。

テストをしていくにあたってClojureの面白いなおもったことがあったので残しておきます。

Javaの場合

まずjavaのテストコードの場合は以下の通り。
ドルクラスを作って、そこから5ドルのインスタンスを生成し、timesメソッドを使って倍化できることを確認するテストである。

public class MoneyTest {
    @Test
    public void testMultiplication() {
        Dollar five = new Dollar(5);
        five.times(2);
        assertEqual(10, five.amount);
        five.times(3);
        assertEqual(15, five.amount);
    }
}

注目すべきは、assertionのとき10や15という数値とfiveの属性であるamount(数値)を比較しており、 オブジェクト同士を比較しているわけではないということである。

Clojureの場合

もちろんClojureの場合も同じようにかける

(deftest multiple-test
  (testing "5ドルを2,3倍すれば10,15ドルになる"
    (let [five (d/->Dollar 5)]
      (is (= 10 (:amount (d/times five 2))))
      (is (= 15 (:amount (d/times five 3)))))))

しかし、数値比較と同じようにオブジェクト同士を比較してもClojureではテストが通る。

(deftest multiple-test
  (testing "5ドルを2,3倍すれば10,15ドルになる"
    (let [five (d/->Dollar 5)]
      (is (= (d/->Dollar 10) (d/times five 2)))
      (is (= (d/->Dollar 15) (d/times five 3))))))

javajavascriptの場合、同じ属性で同一値を持っているオブジェクトであっても 参照値が異なるため単純な比較はできず、deepEqualなどが必要になる。

それに対してClojureはオブジェクト同士を比較できる。 この理由は、Clojureはデータをデフォルトでイミュータブルで扱うことだろうと考えられる。

ある数値に別の数値を掛けたとしても、副作用で互いの数値が別の値に変わることがないように、 それがオブジェクトであってもClojureでは変わらない。だから、=で比較できる。

ではClojureでミュータブルなデータを使用した場合、どうなるのだろうか?

(def zero (atom 0))
(def Zero (atom 0))

(= zero Zero)
;=> false

@ をつけずに比較すると、一致しないことがわかる。

(def five (atom d/->Dollar 5))
(def ten (atom d/->Dollar 10))

(= (d/times @five 2) @ten)
;=> true

@ を使えば比較できる。 結局atom@ で逆参照したとき取得できた値はイミュータブルだからであろう。

デフォルトイミュータブルな世界が為せる面白い挙動なんだろうと感じた。

ClojureDocs=の記述を読めば、 そのとおりだとわかる。

Equality. Returns true if x equals y, false if not. Same as Java x.equals(y) except it also works for nil, and compares numbers and collections in a type-independent manner. Clojure's immutable data structures define equals() (and thus =) as a value, not an identity, comparison.

イミュータブルなデータ構造は、参照比較ではなく値比較してるというわけだ。 なるほど。

テスト駆動開発

テスト駆動開発