Haskellerへの道 #10 - 型クラス

Last Edited: 6/22/2024

このブログ記事では、Haskellにおいて重要な型クラスについて紹介しています。

Haskell & Typeclasses

型クラス

これまで型について広く話してきましたが、VSCodeが頻繁に提案するNumOrdEqについては避けてきました。これらは型ではなく型クラスだからです。 型クラスについては別の記事で詳しく説明したかったのです。型クラスは、オブジェクト指向プログラミングにおけるインターフェースのようなもので、 型クラスに属する型の振る舞いを定義します。例えば、Num型クラスによって強制される振る舞いの一例が+関数です。

ghci> :t (+)
(+) :: Num a => a -> a -> a

+関数の型は、Num型クラスの任意の型が入力および出力として使えることを示しています。:infoフラグを使って、型クラスに定義されている他の関数を調べることができます。

ghci> :info Num
class Num a where
 (+) :: a -> a -> a
 (-) :: a -> a -> a
 (*) :: a -> a -> a
 negate :: a -> a
 abs :: a -> a
 signum :: a -> a
 fromInteger :: Integer -> a
 {-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
        -- Defined in ‘GHC.Num’
instance Num Double -- Defined in ‘GHC.Float’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Word -- Defined in ‘GHC.Num’

ここで注目すべきは、これらの関数について型だけが定義されていることです。これは、型クラスに属する異なる型に対して、 実際のロジックが異なる可能性があるからです。MINIMALと書かれた部分は、その型クラスの有効なインスタンスを作成するために必要な関数を定義しています。 次の部分では、Num型クラスに属するいくつかの事前定義された型がリストされています。では、よく使われる型クラスを見てみましょう。

ghci> :info Show
class Show a where
  showPrec :: Int -> a -> ShowS
  show :: a -> String
  showList :: [a] -> ShowS
  {-# MINIMAL ShowPrec | show #-}

ghci> :info Eq
class Eq a where
 (==) :: a -> Bool
 (/=) :: a -> Bool
 {-# MINIMAL (==) | (/=) #-}

ほとんどの型はShow型クラスに属しており、これは関数の出力をGHCiで文字列として表示するために使われます。また、Eqは等価性のテストを担当します。

使用例

これがどのように役立つのでしょうか?型クラスをTemperatureクラスの例で見てみましょう。

data Temperature = C Float | F Float 
--- CとFのコンストラクタはそれぞれ摂氏と華氏を表します
 
--- Eqを使ってルールを定義
instance Eq Temperature where
  (==) (C n) (C m) = n == m
  (==) (F n) (F m) = n == m
  (==) (C n) (F m) = (1.8*n - 3.2) == m
  (==) (F n) (C m) = (1.8*m - 3.2) == n

instance Eqを使い、Temperature型に対するロジックを定義することで、異なる温度スケール(摂氏、華氏)でも等価性をチェックすることができます。

型クラスの導出

Temperatureのように特別なルールを持たせたくない場合、また単にそのデータ型をEqのインスタンスにするための簡単な等価性チェックを行いたい場合、次の短い構文を使うことができます。

data Temperature = C Float | F Float
  deriving(Eq)

deriving構文を使用すると、Haskellは指定された型クラスの有効なインスタンスになるために必要な最も単純な関数を自動生成できます。 これが簡単で大部分の場合に機能しますが、この構文の使用を避けたい場合もあります。Haskellが自動生成したTemperatureのコードを見てみましょう。

(==) (C n) (C m) = n == m
(==) (F n) (F m) = n == m
(==) _ _ = False

Haskellは、値コンストラクタが同じ場合のみを考慮しています。これは、値コンストラクタの本質的な意味を知り得ないためです。

クイズ

この記事では、学習した内容を確認するためのクイズを設けます。記事のメイン部分を読んだ後に、ぜひ自分で問題を解いてみることを強くお勧めします。各問題をクリックすると答えが表示されます。

リソース