Arantium Maestum

プログラミング、囲碁、読書の話題

Thinking Functionally with Haskell: 第2章再読 TypeとType Classメモ

Type関連で混乱してきたので、ちょっと一旦第2章に戻ってメモをまとめていく。

"In Haskell every well-formed expression has, by definition, a well-formed type. Each well-formed expression has, by definition, a value."

まず、式には型と値がある。Pythonのような言語では「値がまずあって、その値に型がある」と考えるのが自然だが、Haskellでは式の評価もまず「式の型を調べ、正しく型が定まっていれば次に値を評価する」という流れになる。だから上記の引用でも別途型と値についてこの順番で言及しているのだろう。

式の型

式の型は:typeで調べられる。

Prelude> :type 'A'
'A' :: Char

Prelude> :type (head "ABC")
(head "ABC") :: Char

後者は式が値に評価されずに型の情報を出しているのがわかる。

Prelude> :type 1 + 2
1 + 2 :: Num a => a

Prelude> :type 1.0 + 2.3
1.0 + 2.3 :: Fractional a => a

数字を:typeで評価するとすぐに型クラスの概念が顔を出す。型クラスについては後述する。

関数の型

関数にも型がある。C言語などでもおなじみだが、引数と戻り値の型がそのまま関数の型になる。

Prelude> :type toUpper
toUpper :: Char -> Char

toUpperChar型をとってChar型を返す。

Prelude> :type head
head :: [a] -> a

headは任意の型aを含むリストをとり同じ型aを返す。

Prelude> :type (+)
(+) :: Num a => a -> a -> a

(+)Num型クラスに属する型aを二つとり、aを返す。引数と戻り値のaは3つとも同じ型。

型クラス

(+)の引数・戻り値のような「特定の性質を持つ任意の型」を表したい時に型クラスを使う。

例えば==で等価性を調べるような処理を含む関数の場合、==が定義されているどのような型でも引数に使うことができる、逆に==が定義されていない型では使えない、と関数の型定義で指定したい。その場合「Eq型クラスの任意の型a」と表現する。

例えば:

isIn :: (Eq a) => a -> [a] -> Bool
isIn _ [] = False
isIn a (x:xs)
  | a == x = True
  | otherwise = isIn a xs

型の宣言

一番簡単な「新しい型の作り方」は他の型を使って宣言することだろう。

type Uuid = Int
type Name = [Char]

getName :: Uuid -> Name

IntCharのリストとをUuidName型とAliasして使っている。

他の型を使わず1から型を宣言する場合はdata declarationを書く:

data Bool = False | True

FalseTruedata constructor。この概念はイマイチよくわかっていない。

Eq型クラスに属する型を宣言する場合:

data Color = Red | Green | Blue
  deriving (Eq)

あるいは

instance Eq Color where
  x == y = (somef x == somef y)

前者は==は単にどのコンストラクタで作成されたかのみのチェック。後者は自分で==の挙動が定義できる。

HaskellではTypeがType ClassのInstance。PythonC++Javaのように「ObjectがClass/TypeのInstance」という用語の使い方と混同すると危ない。

型クラスの宣言

簡単なEq型クラスの宣言は以下のようになる:

class Eq a where
  (==), (/=) :: a -> a -> Bool
  x /= y = not (x == y)

ある型aEq型クラスのInstanceであるためには、(==)(/=)を正しい型で定義されている必要がある。ただし、(/=)(==)を使ったデフォルト定義が存在するので、実際には(==)を定義するだけでいい。

等価性だけではなく、大きさの比較も可能なOrd型クラスの宣言:

class (Eq a) => Ord a where
  (<), (<=), (>=), (>) :: a -> a -> Bool

OrdEqのsubclassである、と一行目で宣言し、Eqが必要とする関数以外でOrdに必要な関数を二行目で宣言している。