2010年2月8日月曜日

オブジェクトの相互参照 と 関数の相互再帰 (2) – Haskell の Data.Unique 型で一意な値を生成

オブジェクトの相互参照 と 関数の相互再帰 (1)」 の続き。

 

オブジェクト識別子

前回 の Python と Haskell のコードには、言語の性格上異なる点がいくつかある。 (cf. gist: 288970, gist: 297114)  その一つは、Python では変数 tarou がオブジェクト識別子 (OID) により、一つの同一性を持ったオブジェクトとして管理されていること。

Python リファレンスマニュアルの 3.1 オブジェクト、値、および型 によると、

オブジェクトはアイデンティティ値 (identity) 、型 (type) 、そして値 (value) を持ちます。…

関数 id() は、オブジェクトのアイデンティティ値を表す整数 (現在の実装ではオブジェクトのメモリ上のアドレス) を返します。

以下を実行すると、オブジェクトのアイデンティティを表わす値が表示される。

print id(tarou)

これに対して、Haskell では OID のようなものは自動的に生成されない。 Haskell の関心はあくまでも型で指定し、データコンストラクタによって生成された値。等値性に関して言うならば、フィールドを元にした値が対象となる。

 

Python と Haskell の等値性の違い

例えば、前回Python の例 で考えると、コードを実行することによって tarou さんは「結婚 と 離婚 を経験をした状態」となる。コードの続きに、「同名で独身の tarou2 くん」を生成して、

img02-05-2010[1].png

tarou さんと tarou2 くんが同じ人物か確認をすると、(結婚、離婚は省略)

tarou  = Person("Tarou") # 結婚と離婚を経験した太郎さん
tarou2 = Person("Tarou") # 独身の太郎くん
print tarou == tarou     #=> True
print tarou == tarou2    #=> False

オブジェクト識別子により別ものと認識されるので、別人と判定される。(比較のためのメソッドが実装されていないとして。cf. 要素クラスに比較メソッド __cmp__(self, other) を実装)

 

Python と同じつもりで 前回のHaskell のコード を以下のように変更すると、(結婚、離婚は省略)

main = do let tarou  = born "Tarou"  -- 結婚と離婚を経験した太郎さん
	      tarou2 = born "Tarou"  -- 独身の太郎くん
	  print $ tarou == tarou  -- True
	  print $ tarou == tarou2 -- True

tarou さんと tarou2 くんが同じものと判断される。

Person 型は定義において

… deriving Eq

導出インスタンス 宣言がされていた。よって、Person 型のフィールドの値が同じなので、同一人物だと判定される。

 

Data.Unique 型 でオブジェクト識別子を真似る

もし Haskell において Person 型の値をそれぞれ区別したいなら、「値が一意となるフィールド」を持てばよい。例えば、Integer 型のフィールド oid を Person 型に含めるなら、

data Person = Person { oid :: Integer
                     , name :: String
                     , partner :: Partner
                     } deriving Eq

しかし、一意となるように自分で oid を管理するのはちょっと面倒な気がする。 (+_+) 何か便利なライブラリはないものかと探したら、名前からしてそれっぽい Data.Unique モジュールがあった。説明によると、

An abstract interface to a unique symbol generator.

これが使えそうなので、フィールド oid を Unique 型へ変更。

import Data.Unique

data Person = Person { oid :: Unique
                     , name :: String
                     , partner :: Partner
                     } deriving Eq

 

Data.Unique の newUnique 関数

Data.Unique によると、Unique 型の値を生成する関数は、

newUnique :: IO Unique

Creates a new object of type Unique. The value returned will not compare equal to any other value of type Unique returned by previous calls to newUnique. …

newUnique 関数の型を見ると Unique 型の値が IO に包んで返される。 この値を先ほどの oid の値として割り当てればよい。 IO で値が包まれている関数と言えば、お馴染、文字列を読み込むための

getContents :: IO String

がある。main 関数で値を変数に束縛するとき、

do cs <- getContents

のようにするので、これと同じように、

do oid <- newUnique

と使うのがイメージできる。

ところで、IO モナド は Maybe やリストと違い、IO で包んだ中身を取り出せない。The monad laws によると、

標準の Monad クラスではモナドから値を得る手段は定義されていません。…

Haskell の Monad クラスでは、このような関数を要求しないことで、一方向モナドの生成を可能にしています。一方向モナドは値を、return 関数(場合によっては fail 関数)を通じて、モナドに入れることが可能で、モナド中の計算は、バインド関数 >>= および >> を用いて実行することができます。しかし、モナドから値を逆にとり出すことはできません。 …

IO モナドは Haskell におけるよく知られた一方向モナドの例です。IO モナドから脱出することはできませんから、 IO モナド中で計算を行うにもかかわらず、結果の値として型構築子 IO を含まないような関数の定義を書くことは不可能です。…

つまり、newUnique 関数は値を IO で包むので、これを使う関数は値を返すときに IO で包む必要がある。 よって、Person 型の値を生成するとき、return 関数により Unique 型の値を IO で包む。

born n = do oid <- newUnique
            return $ Person oid n Nothing

 

(>>=) の型の確認

… と書いておきながら、何で return で包むのだっけ?と考えてしまった。 ^^;

上記 born 関数を (>>=) で書き直すなら、

born n = newUnique >>= \oid -> return (Person oid n Nothing)

newUnique の型は IO Unique 。これを (>>=) に適用した場合の型を確認すると、

*Main> :t (>>=) newUnique
(>>=) newUnique :: (Unique -> IO b) -> IO b

つまり、(>>=) に与える第1引数が IO Unique である場合、第2引数の型は、

Unique –> IO b

となるので、return により IO で包まなければならない。そうしないと (>>=) で計算をつなげることができない。

 

Person 型の等値性の検査

Unique 型の値で OID のようなものを表現できたので、この値を等値性の検査で利用する。導出インスタンス宣言は削除。

instance Eq Person where
    p1 == p2 = oid p1 == oid p2

 

IO モナドの中の計算

上記 born 関数の型は、

born :: String -> IO Person

main 関数の型は、

main :: IO t

同じ IO モナドを使っているので、main の中で born 関数で生成した Person 型の値を取り出し、他の関数に引き渡して、その結果をまた IO モナドの中に投入できる。

main = do tarou  <- born "Tarou"
          hanako <- born "Hanako"
          let m = marry tarou hanako
              d = divorce m
          print d
          print m

このため、前回の marry, divorce 関数は変更する必要がない。

img02-10-2010[1]

 

全体のコードを示す。

import Data.Maybe
import Data.Unique

data Person = Person { oid     :: Unique
                     , name    :: String
                     , partner :: Partner
                     }
-- パートナー
type Partner = Maybe Person

instance Show Person where
    show (Person oid n Nothing) = n
    show (Person oid  n p)      = n ++ "{" ++ (name $ fromJust p) ++ "}"

instance Eq Person where
    p1 == p2 = oid p1 == oid p2

-- 誕生
born n = do oid <- newUnique
            return $ Person oid n Nothing

-- 結婚
marry p1 p2 = (p1 { partner = Just p2 }, p2 { partner = Just p1 })

-- 離婚
divorce (p1,p2) = (p1 { partner = Nothing }, p2 { partner = Nothing })

main = do tarou  <- born "Tarou"
          hanako <- born "Hanako"
          let m = marry tarou hanako
              d = divorce m
          print d  -- (Tarou,Hanako)
          print m  -- (Tarou{Hanako},Hanako{Tarou})

          tarou2 <- born "Tarou"
          print $ tarou == tarou  -- True
          print $ tarou == tarou2 -- False

これで以前よりもオブジェクトを使うときのような雰囲気になった。では、次に複数のオブジェクトの状態が変化していく様を模倣するにはどうすればいいのだろう?