Real World Haskell - 第6章 Using Typeclasses

Real World HaskellHaskell を継続的に勉強中。

いろいろあって、しばらく間が空いてしまったが、なんとかペースを取り戻すぞ。

Chapter 6. Using Typeclasses
Chapter 6. Using Typeclasses

The need for typeclasses

  • == のように異なる型を比較する場合でも同じ名前を使えると便利
    • そういう generic なコードを書くのに typeclasses が使える

What are typeclasses?

typeclass の定義

class BasicEq a where
    isEqual :: a -> a -> Bool

instance の作成

instance BasicEq Bool where
    isEqual True  True  = True
    isEqual False False = True
    isEqual _     _     = False

default implementation を書ける

class BasicEq3 a where
    isEqual3 :: a -> a -> Bool
    isEqual3 x y = not (isNotEqual3 x y)

    isNotEqual3 :: a -> a -> Bool
    isNotEqual3 x y = not (isEqual3 x y)

Important Built-In Typeclasses

  • Show
    • 値を文字列に変換する
      • ghci で表示するために使ってるよね
    • putStrLn と使い分けましょう
  • Read
    • Show と反対に文字列をある型の値にする
    • 型は明示的に指定すべし
main = do
        putStrLn "Please enter a Double:"
        inpStr <- getLine
        let inpDouble = (read inpStr)::Double
        putStrLn ("Twice " ++ show inpDouble ++ " is " ++ show (inpDouble * 2))

Serialization with Read and Show

Show と Read はシリアライズに使える

Automatic Derivation

  • 下記の typeclass については自動導出可能
    • Read
    • Show
    • Bounded
    • Enum
    • Eq
    • Ord
data Color = Red | Green | Blue
     deriving (Read, Show, Eq, Ord)

More helpful errors

  • エラーを表示するのには Maybe より Either が便利
data Maybe a = Nothing
             | Just a
               deriving (Eq, Ord, Read, Show)

data Either a b = Left a
                | Right b
                  deriving (Eq, Ord, Read, Show)

Making an instance with a type synonym

  • pragma を書けば GHC 拡張が使える
    • typeclass の instance で型のシノニム (String など) を使いたい場合
{-# LANGUAGE TypeSynonymInstances #-}

Living in an open world

  • open world assumption
    • typeclass を定義した module に限らず、どこでも instance を定義できる
When do overlapping instances cause problems?
  • instance の関数適用が曖昧になる場合はエラーが出る
  • OverlappingInstances pragma を覚えておくべし

How to give a type a new identity

  • 既存の型と同じように扱える別の型を newtype で定義できる
newtype NewtypeInt = N Int
    deriving (Eq, Ord, Show)
Differences between data and newtype declarations
  • newtype は data に比べて bookkeeping cost がかからない
  • newtype では value constructor は 1つだけ、フィールドも 1つしか持てない
-- ok: any number of fields and constructors
data TwoFields = TwoFields Int Int

-- ok: exactly one field
newtype Okay = ExactlyOne Int

-- ok: type parameters are no problem
newtype Param a b = Param (Either a b)

-- ok: record syntax is fine
newtype Record = Record {
      getInt :: Int
    }

-- bad: no fields
newtype TooFew = TooFew

-- bad: more than one field
newtype TooManyFields = Fields Int Int

-- bad: more than one constructor
newtype TooManyCtors = Bad Int
                     | Worse Int
Summary: the three ways of naming types
  • data は代数データ型の定義に使う
  • type は既存の型のシノニムの定義に使う
    • 既存の型とシノニムは可換
  • newtype は既存の型に別の名前を付ける
    • 既存の型と新しい型は非可換

The dreaded monomorphism restriction

これはエラーになる

myShow = show

こうすると問題ない

myShow2 value = show value

myShow3 :: (Show a) => a -> String
myShow3 = show
  • GHC が the monomorphism restriction でエラーを吐いた場合の対応方法
    • 関数の引数を明示的に書く
    • 型推論させずに型を明示的に書く
    • NoMonomorphismRestriction を有効にしてコンパイルする

the monomorphism restriction について詳しく知りたいなら Haskell 98 Report を読むべし
The Haskell 98 Report: Declarations