すごいHaskell読書会
in 大阪 2週目 #8
宇佐見 公輔
第7章
型や型クラスを自分で作ろう
(後編)
前編のおさらい
4 新しいデータ型を定義する
4 レコード構文
4 型引数
4 インスタンスの自動導出
4 型シノニム
(前編のおさらい)
新しいデータ型を定義する
data 型名 = 値コンストラクタ
data Bool = False | True
data Shape = Circle Float Float Float |
Rectangle Float Float Float Float
data Point = Point Float Float
(前編のおさらい)
レコード構文
data Person = Person { firstName :: String
, lastName :: String
, age :: Int
, height :: Float
, phoneNumber :: String
, flavor :: String }
(前編のおさらい)
型引数
data 型コンストラクタ = 値コンストラクタ
(型名 型引数)
data Maybe a = Nothing | Just a
data Either a b = Left a | Right b
-- 3次元ベクトルの例
data Vector a = Vector a a a
(前編のおさらい)
インスタンスの自動導出
data Day = Monday | Tuesday | Wednesday | Thursday |
Friday | Saturday | Sunday
deriving (Eq, Ord, Show, Read, Bounded, Enum)
-- 型クラスの実装が補われ、以下が可能になる
ghci> Saturday == Sunday
False
ghci> show Wednesday
Wednesday
(前編のおさらい)
型シノニム
type String = [Char]
type Name = String
type PhoneNumber = String
type PhoneBook = [(Name, PhoneNumber)]
-- 以下の例では AssocList は型コンストラクタ
type AssocList k v = [(k, v)]
後編の内容
4 再帰的なデータ構造
4 型クラス中級講座
4 Yes と No の型クラス
4 Functor 型クラス
4 型を司るもの、種類
再帰的なデータ型
再帰的なデータ型
4 値コンストラクタのフィールドに自分自身の型を持たせる
4 例えば、リストは再帰的なデータ型
4 [3,4,5,6] は 3:[4,5,6] である
4 3:4:[5,6] でもある
4 3:4:5:[6]
4 3:4:5:6:[]
例:独自リスト型
data List a = Empty
| Cons a (List a)
deriving (Show, Read, Eq, Ord)
-- レコード構文の場合
data List a = Empty
| Cons { listhead :: a
, listtail :: List a }
deriving (Show, Read, Eq, Ord)
独自リスト型のリスト表現
4 先の独自定義の Cons は標準の : にあたるもの
Empty -- [] にあたる
5 `Cons` Empty -- 5:[] にあたる
4 `Cons` (5 `Cons` Empty) -- 4:5:[] にあたる
3 `Cons` (4 `Cons` (5 `Cons` Empty)) -- 3:4:5:[] にあたる
独自リスト型の改善
4 値コンストラクタを中置関数に(: で始めること)
4 結合の優先度を定義(infixl や infixr を使う)
infixr 5 :-:
data List a = Empty
| a :-: (List a)
deriving (Show, Read, Eq, Ord)
改善結果
4 リストをこう書けるようになった
5 :-: Empty
-- 5 `Cons` Empty
4 :-: 5 :-: Empty
-- 4 `Cons` (5 `Cons` Empty)
3 :-: 4 :-: 5 :-: Empty
-- 3 `Cons` (4 `Cons` (5 `Cons` Empty))
例:独自リスト型の結合
infixr 5 ^++
(^++) :: List a -> List a -> List a
Empty ^++ ys = ys
(x :-: xs) ^++ ys = x :-: (xs ^++ ys)
4 実はパターンマッチとは、値コンストラクタのマッチ
4 標準の : も値コンストラクタ
別の例:二分探索木
data Tree a = EmptyTree
| Node a (Tree a) (Tree a)
deriving (Show)
4 二分探索木:各要素について、
4 左部分木の全要素がその要素より小さい
4 右部分木の全要素がその要素より大きい
木に要素を追加する関数
4 与えられた木の直接更新は不可、新しい木を作って返す
singleton :: a -> Tree a
singleton x = Node x EmptyTree EmptyTree
treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x EmptyTree = singleton x
treeInsert x (Node y left right)
| x == y = Node y left right -- 同じなら挿入しない
| x < y = Node y (treeInsert x left) right
| x > y = Node y left (treeInsert x right)
木の生成
ghci> let nums = [8,6,4,1,7,3,5]
ghci> let numsTree = foldr treeInsert EmptyTree nums
4 tree1 = treeInsert 5 EmptyTree
4 tree2 = treeInsert 3 tree1
4 tree3 = treeInsert 7 tree2
4 ...
4 numsTree = treeInsert 8 tree7
木に要素が属するか調べる関数
treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem x EmptyTree = False
treeElem x (Node y left right)
| x == y = True
| x < y = treeElem x left
| x > y = treeElem x right
型クラス中級講座
型クラス中級講座
4 型クラスの復習
4 ある型 T がある型クラス C のインスタンス
= 型 T に対して型クラス C が定義する関数を使える
4 型クラスを自分で作るには?
Eq 型クラスの宣言
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)
4 a は型引数で、Eq のインスタンスとなる型
Eq 型クラスのインスタンス宣言
data TrafficLight = Red | Yellow | Green
instance Eq TrafficLight where
Red == Red = True
Yellow == Yellow = True
Green == Green = True
_ == _ = False
4 注意:通常は deriving で自動導出する
最小完全定義
x == y = not (x /= y)
x /= y = not (x == y)
4 Eq のインスタンスは == か /= のどちらかを定義する必要が
ある
4 片方を定義(デフォルト実装を上書き)すれば、もう片方
も定義される
Show 型クラスのインスタンス宣言
4 自動導出での定義でなく、自前で定義したい場合に
instance Show TrafficLight where
show Red = "Red light"
show Yellow = "Yellow light"
show Green = "Green light"
型クラス宣言に型クラス制約をつけ
る
4 Num 型クラスは Eq 型クラスのサブクラス
class (Eq a) => Num a where
...
Maybe を Eq のインスタンスにす
る?
class Eq a where
(==) :: a -> a -> Bool
4 型引数 a は具体型でなくてはダメ(関数定義にあわない)
4 Maybe は具体型でなく多相型(型コンストラクタ)
4 じゃあ具体型 Maybe Char を Eq のインスタンスにする?
4 それでは Maybe Int は? ・・・きりがない
多相型を型クラスのインスタンスに
する
4 インスタンス宣言に型引数をつける
instance Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False
4 注意:この宣言は不十分(次ページ参照)
インスタンス宣言に型クラス制約を
つける
4 型 m が Eq 型クラスでないとダメ
4 型クラス制約をつける
instance (Eq m) => Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False
再度整理してみる
4 Eq 型クラス宣言では、a が具体型でないとダメ
4 (==) :: a -> a -> Bool と使われるから
4 a に Maybe は渡せない
4 (==) :: Maybe -> Maybe -> Bool はダメ
4 a に Maybe m なら渡せる
4 (==) :: (Eq m) => Maybe m -> Maybe m -> Bool
型クラスの情報
4 ghci で :info Maybe などとすれば情報が得られる
Yes と No の型クラス
Yes と No の型クラス
4 お題:型クラスを作ってみる
Yes と No の型クラス
4 Haskell では真理値が必要な箇所では厳密に Bool 型を使う
4 他の言語ではそうでないものもある:JavaScript の例
4 if (0)
4 if ("")
4 if (false)
4 類似のことを独自の型クラスで実現してみる
型クラス宣言とインスタンス宣言
class YesNo a where
yesno :: a -> Bool
instance YesNo Int where
yesno 0 = False
yesno _ = True
instance YesNo [a] where
yesno [] = False
yesno _ = True
instance YesNo Bool where
yesno = id -- そのまま返す
instance YesNo (Maybe a) where
yesno Nothing = False
yesno (Just _) = True
instance YesNo (Tree a) where
yesno EmptyTree = False
yesno _ = True
instance YesNo TrafficLight where
yesno Red = False
yesno _ = True
if の代替
yesnoIf :: (YesNo y) => y -> a -> a -> a
yesnoIf yesnoVal yesResult noResult =
if yesno yesnoVal
then yesResult
else noResult
ghci> yesnoIf [] "YEAH!" "NO!"
"NO!"
ghci> yesnoIf [2,3,4] "YEAH!" "NO!"
"YEAH!"
Functor 型クラス
Functor 型クラス
class Functor f where
fmap :: (a -> b) -> f a -> f b
4 この f は具体型ではなく、型コンストラクタ
4 引数1 : 型 a から型 b への関数型
4 引数2 : 型コンストラクタ f に型引数 a を適用した型
4 戻り値 : 型コンストラクタ f に型引数 b を適用した型
リストの map
-- リストの map
map :: (a -> b) -> [a] -> [b]
-- Functor の fmap
fmap :: (a -> b) -> f a -> f b
4 map はリスト限定で動作する fmap だといえる
リストの Functor インスタンス宣言
instance Functor [] where
fmap = map
4 ここで、[] は型コンストラクタであることに注意
4 [Int] [String] などが具体型
Maybe の Functor インスタンス宣言
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
4 Maybe は型コンストラクタ
二分探索木の Functor インスタンス
宣言
instance Functor Tree where
fmap f EmptyTree = EmptyTree
fmap f (Node x left right)
= Node (f x) (fmap f left) (fmap f right)
4 Tree は型コンストラクタ(Tree a が具体型)
4 注意:任意の関数を適用した後は二分探索木の性質を保た
ない
Either の場合は?
4 Either は型引数を 2 つとる型コンストラクタ
4 Either a b が具体型
4 1 つだけ部分適用すると Functor にできる
4 Either a は型引数を 1 つとる型コンストラクタ
Either の Functor インスタンス宣言
instance Functor (Either a) where
fmap f (Right x) = Right (f x)
fmap f (Left x) = Left x
4 data Either a b = Left a | Right b
4 Left と Right で型が違うことに注意
4 fmap で適用する関数 f は b -> c 型
4 Right 側には適用できるが、Left 側には適用できない
Data.Map の場合は?(練習問題)
4 Map k v は k 型がキー、v 型が値のデータ構造
4 fmap で適用する関数は v -> v' 型
4 問題:どのように Functor のインスタンスになるか?
4 MyFunctor 型クラス宣言を書く
4 Map の MyFunctor インスタンス宣言を書く
4 Map に myfmap で関数を適用する
Functor について
より深い内容は後の章で
型を司るもの、種類
型を司るもの、種類
4 型の種類(kind)
4 GHCi の :k コマンドで型の種類を調べる
型の種類
ghci> :k Int
Int :: *
ghci> :k Maybe
Maybe :: * -> *
ghci> :k Maybe Int
Maybe Int :: *
4 * は具体型を表す記号
4 Maybe は具体型を引数にとって具体型を返す
値、型、種類
4 値のラベルが型
4 型のラベルが種類
4 GHCi の :t コマンドは値の型を調べる
4 GHCi の :k コマンドは型の種類を調べる
型コンストラクタへの部分適用
4 型コンストラクタがカリー化されていて部分適用できる
ghci> :k Either
Either :: * -> * -> *
ghci> :k Either String
Either String :: * -> *
Functor になれる型の種類
class Functor f where
fmap :: (a -> b) -> f a -> f b
4 f a f b の種類は *(関数の型宣言に使われているため)
4 f の種類は * -> * でなくてはならない
第7章後編おわり
4 再帰的なデータ構造
4 型クラス中級講座
4 Yes と No の型クラス
4 Functor 型クラス
4 型を司るもの、種類

すごいHaskell読書会