tak0kadaの何でもノート

発声練習、生存確認用。

医学関連は 医学ノート

「Learn You a Haskell for Great Good!」第7章を読んだ

第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)
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 :: *