型クラス

このとき 本当は ArrowChoice*1 での実装も書く予定だったけどまだ理解が足りてなかった。
それで id:nskj77 氏が書かれた Arrow のはなし を読んでいる。
読みながら実際の定義も調べているとどうやら型クラスが理解できていない。
型クラスについて調べてみる。

『Real World Haskell』 から引用
6.2 型クラスとは何か


型クラスは実際に与えられたデータの型ごとに実装を持ち得る関数の集合によって定義するものです。型クラスはオブジェクト指向プログラミングのオブジェクトに似ているようにも思えますが、全く違うものです。

『プログラミング Haskell』 から引用
3.9 基本クラス


型は互いに関連する値の集合であったことを思い出そう。この考えたに則して言えば、クラスとは、共通のメソッドを提供する型の集合である。メソッドとは多重定義された関数のことである。

自己流に理解すると

  • クラスとは何かを分類するためのもの(XXX はクラスに属しているかを問える)
    • Groovy ではオブジェクトを分類する
    • Haskell ではデータ型を分類する
  • Groovy ではクラスと呼ばれるものが Haskell でデータ型と呼ばれる

data と newtype の違いは型クラスとは直接関係ないので

  • newtype は 1 つだけ フィールドを持つ
  • コンストラクタも 1 つ

という理解にしておく。


実際に Arrow のソースを読む

-- データ型 a のための Arrow クラスを定義する場合は以下のものを実装しなければならない
-- データ型 a は Category クラスでなければならない
-- ちなみに Category クラスのメソッドは id と (.)
class Category a => Arrow a where
        arr :: (b -> c) -> a b c
        first  :: a b c -> a (b,d) (c,d)
        second :: a b c -> a (d,b) (d,c)
        -- second のデフォルトの実装
        second f = arr swap >>> first f >>> arr swap
                        where   swap :: (x,y) -> (y,x)
                                -- ~ は遅延パターンだが今回は無視してもよい
                                swap ~(x,y) = (y,x)
        (***) :: a b c -> a b' c' -> a (b,b') (c,c')
        f *** g = first f >>> second g
        (&&&) :: a b c -> a b c' -> a b (c,c')
        f &&& g = arr (\b -> (b,b)) >>> f *** g


-- (->) 型 のための Arrow クラスの実装
instance Arrow (->) where
        arr f = f
        first  f = f *** id
        second f = id *** f
        (***) f g ~(x,y) = (f x, g y)


(->) がデータ型であることが不思議な感じだが調べてみるとやはりデータ型だ。

Prelude> :i (->)
data (->) a b   -- Defined in GHC.Prim


続きを読む

-- データ型 Kleisli を定義する
-- Kleisli は runKleisli フィールドを持つ
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }

-- Arrow クラスを実装するには Category クラスも実装しておかなければいけない
-- Monad m => は、ここで m は Monad クラスのメソッドが定義されているデータ型の意味
-- Kleisli の runKleisli の戻り値のデータ型が Monad クラスである場合の Category クラスの実装
instance Monad m => Category (Kleisli m) where
        id = Kleisli return
        (Kleisli f) . (Kleisli g) = Kleisli (\b -> g b >>= f)

-- 同じく Arrow クラスの実装
instance Monad m => Arrow (Kleisli m) where
        arr f = Kleisli (return . f)
        first  (Kleisli f) = Kleisli (\ ~(b,d) -> f b >>= \c -> return (c,d))
        second (Kleisli f) = Kleisli (\ ~(d,b) -> f b >>= \c -> return (d,c))

Kleisli が何を意味しているのかはまだわからないけどソースは読めるようになった。

-- Control.Arrow モジュールの関数定義
-- a は Arrow クラスのメソッドが実装されているデータ型である
returnA :: Arrow a => a b b
returnA = arr id

(^>>) :: Arrow a => (b -> c) -> a c d -> a b d
f ^>> a = arr f >>> a

(>>^) :: Arrow a => a b c -> (c -> d) -> a b d
a >>^ f = a >>> arr f

(<<^) :: Arrow a => a c d -> (b -> c) -> a b d
a <<^ f = a <<< arr f

(^<<) :: Arrow a => (c -> d) -> a b c -> a b d
f ^<< a = arr f <<< a

以下、定義が続くけど読み方は同じ。

まとめ
  • Haskell のデータ型は Groovy ではクラスにあたるもの
  • Haskell ではクラスはデータ型を分類するためのもの
  • データ型があるクラスであるとはそのデータ型のためのクラスのメソッドがすべて実装されていることを意味する

*1:再帰が3つに分岐しているので