第7章は型と型クラスの作りかたがメインでついでにEitherとファンクタの説明がある。
Either
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
EitherはMaybeに似ていて、関数の計算が失敗したときに違う値を返せるようになっている。MaybeはNothingしか返せないのに対し、EitherはLeft aを返すことができるので失敗した原因について情報がほしい時に用いる。
型シノニム
type String = [Char] type AssocList k v = [(k, v)] type IntAssocList v = [(Int, v)] -- type IntMap v = Map Int vと同じ type IntMap = Map Int
型の作り方
1. 列挙
data 型 = 値
data Bool = False | True data Int = -9223372036854775808 | .. | 9223372036854775807
2. 値コンストラクタ
data 型 = 値コンストラクタ
data Shape = Circle Float Float Float | Rectangle Float Float Float Float :t Circle -- Circle :: Float -> Float -> Float -> Shape area :: Shape -> Float area (Circle _ _ r) = pi * r ^ 2 area (Shape x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1) -- ちょっと改善 data Point = Point Float Float deriving (Show) data Shape = Circle Point Float | Rectangle Point Point deriving(Show)
このモジュールをエクスポートする場合
- 型と一緒に値コンストラクタもエクスポートする場合
module Shapes ( Point(..) , Shape(..) , .. ) where
Point(..)はPoint(Circle, Rectangle)と書いてもいい。
- 値コンストラクタはエクスポートしない場合
module Shapes ( Point , Shape , .. ) where
(..)を書かずに型の名前のみ記述。こうすれば型の実装を隠すことができる。
3. レコード
data 型 = 値コンストラクタ {フィールド :: 型, ..}
data Car = Car { company :: String , model :: String , year :: Int } deriving (Show)
値の生成時に必要な項目が多くなると(Car _ model _)
などと書く必要があるため_
だらけになってしまい、関数を作るのが大変になる。レコードを使えばcompany :: Car -> String
などという関数が自動で作られるので書く必要がなくなる。
mycar = Car {company="Toyota", model="Prius", year=2014}
4. 型コンストラクタ
data 型コンストラクタ = ?
data Maybe = Nothing | Just a
型コンストラクタを使うことで多相にできる。型引数を与えれば具体型になる。
:t Just 1 Just 1 :: Num a => Maybe a :t Just 1 :: Maybe Double Just 1 :: Maybe Double :: Maybe Double
今までに出てきた具体型は具体的な型を与えられていたが、それらを型引数に変えると多相になる(意味があるかは状況による)。
多相にしたついでに型制約をデータ型につけることもできるが、不必要にコードを書く必要が出てきて厄介なので付けてはいけない。data (Ord k) => k -> v
などは不可。
- 参考
data Vector a = Vector a a a somefunc :: Vector a a a -> a -- エラー somefunc :: Vector a -> a -- OK!
再帰的なデータ構造の例
- 例1: リストの実装
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord) 4 `Cons` (5 `Cons` (6 `Cons` Empty)) -- あるいは data List a = Empty | a :-: (List a) deriving (Show, Read, Eq, Ord)
- 例2: 木構造
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)
型クラスの作り方
1. 定義
class (型クラスの名前) a where -- 関数 typefunc :: 型
のように定義する。
Eqを例にすると、
class Eq a where -- 型宣言は必須 (==) :: a -> a -> Bool (/=) :: a -> a -> Bool -- 関数は必須ではない x == y = not (x /= y) x /= y = not (x == y)
型クラスによってはその方クラスに属する前に別の型クラスに属する必要があるときがある。(eg. Numの前にEq)
class (Eq a) => Num a where
- 型がどの型クラスに属しているかは
:i(nfo) 型
とすればいい
2.1. 型クラスを導出させる。
特に既存の型クラス: Eq, Show, Read, Ordなど
- Readはreadで読むときに型を指定しないといけないことがある。
read "Just 3" :: Maybe Int
- Ordはデータ型を定義するときに値を並べた順に対応する。
data Bool = False | True deriving (Ord) False < True -- True Nothing < Just 1 -- True compare Just 3 Just 1 -- GT Just 1 < Just 3 -- True data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Ord, Show, Read, Bounded, Enum)
2.2 自分で型クラスに所属させる
instance (型クラス) (型) where .. 必要な関数(型クラスの定義に書いたやつ)
ファンクタ
箱で考えるFunctor、ApplicativeそしてMonadが分かりやすい。
ファンクタも型クラスである。定義は
class Functor f where fmap :: (a -> b) -> f a -> f b
つまりmapが定義されるような入れ物クラス(* -> *
)と考えられる。
前述の木構造では
instance Functor Tree where fmap f EmptyTree = EmptyTree fmap f (Node x left right) = Node (f x) (fmap f left) (fmap f right)
種(kind)
:k Int
などとすると見ることができる。型が関数のラベルなのと同様に、種は型の振る舞いのラベルになっている。
:k Maybe -- Maybe :: * -> * :k Either -- Either :: * -> * -> * :k Maybe Int -- Maybe Int :: *