型や型クラスを自分で
作ろう (前編)
すごいHaskell読書会 in 大阪 2週目 #7
2014-07-16
Suguru Hamazaki
Making Our Own Types and Type Classes
7章前半の内容
データ型の
定義方法
レコード構文
型引数 インスタンスの
自動導出
型シノニム
新しいデータ型を
定義する
Defining a New Data Type
• 標準ライブラリーでどのように定義されている
か?
• Bool
• Int
data Bool = False | True
型名 値コンスト
ラクター
値コンスト
ラクター
data Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647
実際の定義とは異なるが、
このように考えられる
形づくる
Shaping Up
• Shape型
• 2次元上の円と長方形を表現する
• area関数
• Shape型のデータの面積を求める
data Shape = Circle Float Float Float | Rectangle Float Float Float Float	
deriving (Show)
型名
値コンスト
ラクター
値コンスト
ラクター
値コンストラクター
• 値を作るので、値コンストラクター
• 実際には関数の一種
• ex) Circle は Float 型の値を3つ引数として受け取り、Shape
型の値を返す関数
• ex) False は?
• 型と値コンストラクターを混同しないよう注意
• 型名と値コンストラクターの名前が同じでもよい
Value Constructors
area :: Shape -> Float	
area (Circle _ _ r) = pi * r ^ 2	
area (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
型シグネチャーには、
型名であるShape を使う
(Circle や Rectangle は型名ではない)
パターンマッチには
コンストラクターが使える
• Point型
• Circle, Rectangle のフィールドを構成する中間的な
型
• nudge関数
• Shape型のデータを移動
• baseCircle, baseRect関数
• ファクトリー的なもの
data Point = Point Float Float deriving (Show)	
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
Circle, Rectangle のフィールドを
Point 型で定義して整理
area :: Shape -> Float	
area (Circle _ r) = pi * r ^ 2	
area (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
パターンマッチも
若干すっきり (?)
nudge :: Shape -> Float -> Float -> Shape	
nudge (Circle (Point x y) r) a b = Circle (Point (x + a) (y + b)) r	
nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1 + a) (y1 + b))
(Point (x2 + a) (y2 + b))	
!
baseCircle :: Float -> Shape	
baseCircle r = Circle (Point 0 0) r	
!
baseRect :: Float -> Float -> Shape	
baseRect width height = Rectangle (Point 0 0) (Point width height)	
baseCircle
baseRectangle
モジュールからエクスポート
module Shapes	
( Point(..)	
, Shape(..)	
, area	
, nudge	
, baseCircle	
, baseRect	
) where
値コンストラクターを
全てエクスポートする記法
明示的に列挙してもよい
• 型のみエクスポートして、値コンストラクターを
エクスポートしないのも OK
• 実装を隠 できる
レコード構文
Record Syntax
data Person = Person String String Int Float String String deriving (Show)	
!
firstName :: Person -> String	
firstName (Person firstname _ _ _ _ _) = firstname	
!
lastName :: Person -> String	
lastName (Person _ lastname _ _ _ _) = lastname	
!
age :: Person -> Int	
age (Person _ _ age _ _ _) = age	
!
height :: Person -> Float	
height (Person _ _ _ height _ _) = height	
!
phoneNumber :: Person -> String	
phoneNumber (Person _ _ _ _ number _) = number	
!
flavor :: Person -> String	
flavor (Person _ _ _ _ _ flavor) = flavor
data Person = Person { firstName :: String	
, lastName :: String	
, age :: Int	
, height :: Float	
, phoneNumber :: String	
, flavor :: String } deriving (Show)	
それぞれ、フィールド名とその
型名を指定する
ghci> :t firstName	
firstName :: Person -> String	 対応する関数が自動的
に作られる
data Car = Car { company :: String	
, model :: String	
, year :: Int } deriving (Show)	
ghci> Car { company = "Ford", model = "Mustang", year = 1967}	
Car {company = "Ford", model = "Mustang", year = 1967}	
ghci> Car { company = "Ford", year = 1967, model = "Mustang"}	
Car {company = "Ford", model = "Mustang", year = 1967}	
フィールドを任意の順
番で指定できる
(型クラス Show を
derive した場合) 出力
が整形される
• レコード構文が役に立つケース
• フィールドが複数あって、
• どれがどれに対応するのかわかりにくい場合
型引数
Type Parameters
data Maybe a = Nothing | Just a
型コンストラクター
型引数
• 値コンストラクターは、
• 値を引数に取り、
• 値を作る
• 型コンストラクターは、
• 型を引数に取り (型引数)、
• 型を作る
• Maybe は型ではなく、型コンストラクター
• Maybe Char は型
• Just ‘a’ は Maybe Char 型の値
• Nothing は Maybe a 型の値
• Maybe a は polymorphic な型
• Maybe Int, Maybe Char, etc. として振る舞える
型引数を取らなくても
(0個取っても)
型コンストラクターって
言うのかな?
Carをパラメーター化すると?
data Car a b c = Car { company :: a	
, model :: b	
, year :: c } deriving (Show)
tellCar :: (Show a) => Car String String a -> String	
tellCar (Car { company = c, model = m, year = y}) =	
"This " ++ c ++ " " ++ m ++ " was made in " ++ show y	
tellCar では year の型
しかパラメーター化さ
れてない
結局、ほとんどのケースで
Car String String Int 型を
使うことになりそう
型引数を使うと良いケース
• 値コンストラクターに含まれる型が、どんなも
のでも構わないケース
• ex) Maybe a, [a], Data.Map k a
データ宣言に型クラス制約は
加えない
data (Ord k) => Map k v = ...
このような制約は
(文法上は正しいが)
規約上、付けない
• 必要な関数の型宣言に付ければ済む
• 必要の無い関数の型宣言に付けないで済む
3次元ベクトルを表わす
Vector a 型 の場合
data Vector a = Vector a a a deriving (Show)	
!
vplus :: (Num a) => Vector a -> Vector a -> Vector a	
(Vector i j k) `vplus` (Vector l m n) = Vector (i + l) (j + m) (k + n)	
!
dotProd :: (Num a) => Vector a -> Vector a -> a	
(Vector i j k) `dotProd` (Vector l m n) = i * l + j * m + k * n	
!
vmult :: (Num a) => Vector a -> a -> Vector a	
(Vector i j k) `vmult` m = Vector (i * m) (j * m) (k * m)	
型クラス制約は
付けない
関数の方に型クラ
ス制約を付ける
インスタンスの
自動導出
Derived Instances
型クラス
• オブジェクト指向プログラミングにおける「クラス」と混
同しないよう注意
• OOPのクラスは、そのクラスから作られたオブジェクト
が持つ性質を規定
• Haskell の型クラスは、型が型クラスのインスタンスにな
り、その型の性質を定義する
• deriving キーワードを使って、ある型を型クラスのインスタ
ンスにすることができる
data Person = Person { firstName :: String	
, lastName :: String	
, age :: Int	
} deriving (Eq)
1. Person型の値同士を == または /= で比べた時、マッチする値コンスト
ラクターを探す
2. 見付かった値コンストラクターのフィールド同士を、それぞれ == また
は /= で比べる
• 全てのフィールドの型が Eq のインスタンスでなければならない
mikeD = Person { firstName = "Michael"	
, lastName = "Diamond"	
, age = 43 }	
!
adRock = Person { firstName = "Adam"	
, lastName = "Horovitz"	
, age = 41 }	
!
mca = Person { firstName = "Adam"	
, lastName = "Yauch"	
, age = 44 }	
ghci> mikeD == adRock	
False	
ghci> mikeD == mikeD	
True	
ghci> mikeD == Person { firstName = "Michael", lastName = "Diamond", age = 43 }	
True
• Eq
• equality / inequality についてテストできる
• Show
• 値をStringへ変換できる
• Read
• Stringをパーズして値を作れる
• Ord
• 大小比較、順序付けできる
• Bounded
• 上限 (maxBound) 、下限 (minBound) がある
• Enum
• 順番に列挙することができる
data Person = Person { firstName :: String	
, lastName :: String	
, age :: Int	
} deriving (Eq, Show, Read)	
!
mikeD = Person { firstName = "Michael"	
, lastName = "Diamond"	
, age = 43 }	
!
mysteryDude = "Person { firstName = "Michael"" ++	
", lastName = "Diamond"" ++	
", age = 43}"	
ghci> read mysteryDude :: Person	
Person {firstName = "Michael", lastName = "Diamond", age = 43}	
ghci> read mysteryDude == mikeD	
True	
String 型を read
して Person 型に
型クラス Show の show が使えるので、
値をGHCi コンソールに出力できる
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday	
deriving (Eq, Ord, Show, Read, Bounded, Enum)	
!
λ> Saturday > Sunday	
False	
λ> Monday `compare` Wednesday	
LT	
λ> minBound :: Day	
Monday	
λ> maxBound :: Day	
Sunday	
λ> succ Monday	
Tuesday	
λ> pred $ succ Monday	
Monday	
λ> [Thursday .. Sunday]	
[Thursday,Friday,Saturday,Sunday]	
λ> [minBound .. maxBound] :: [Day]	
[Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday]	
Ord による大小比較
Bound による上限
と下限
Enum による順次的な
列挙
上限、下限を利用した、
順次的な列挙
型シノニム
Type Synonyms
type String = [Char]
• 既存の型の別名を定義
• 新しい型が作られるのではない
type PhoneNumber = String	
type Name = String	
type PhoneBook = [(Name,PhoneNumber)]
inPhoneBook :: Name -> PhoneNumber -> PhoneBook -> Bool	
inPhoneBook name pnumber pbook = (name, pnumber) `elem` pbook
より明確な意図を伝える
型シグネチャー
!
!
!
book = [("betty", "555-2938")	
,("bonnie", "452-2928")	
,("patsy", "493-2928")	
,("lucille", "205-2928")	
,("wendy", "939-8282")	
,("penny", "853-2492")	
]	
λ> inPhoneBook "wendy" "939-8282" book	
True
型シノニムの型パラメーター化
type AssocList k v = [(k, v)]
type IntMap v = Map Int v
型パラメーターを持つ
型シノニムの定義
型パラメーターを
部分適用した型シノニム
AssocList, IntMap は
型コンストラクターになる
!
値コンストラクターと
混同しないよう注意
data Either a b = Left a | Right b	
deriving (Eq, Ord, Read, Show)
• ある型か他のある型の値を表現
• Maybe a と同様に、処理の結果を表わすのに
よく使われる
• 失敗した場合もデータを保持できるのが違
い
ロッカーの例
import qualified Data.Map as Map	
!
data LockerState = Taken | Free deriving (Show, Eq)	
!
type Code = String	
!
type LockerMap = Map.Map Int (LockerState, Code)	
!
lockerLookup :: Int -> LockerMap -> Either String Code	
lockerLookup lockerNumber map = case Map.lookup lockerNumber map of	
Nothing -> Left $ "Locker " ++ show lockerNumber ++ " doesn't exist!"	
Just (state, code) -> if state /= Taken	
then Right code	
else Left $ "Locker " ++ show lockerNumber ++ " is already taken!"	
!
lockers :: LockerMap	
lockers = Map.fromList	
[(100,(Taken, "ZD39I"))	
,(101,(Free,"JAH3I"))	
,(103,(Free,"IQSA9"))	
,(105,(Free,"QOTSA"))	
,(109,(Taken,"893JJ"))	
,(110,(Taken,"99292"))]
練習問題
• 上のような構造を持つ、URIを表現する型を作ってみましょう
• query は複数の key, value のペアを持ちます
• 必須の要素とオプショナルの要素があることに注意して下さい
https://user:password@example.com:80/path/somewhere?foo=bar#baz
scheme userinfo host port path query fragment
authority

すごいHaskell読書会 第7章 (前編)

  • 1.
    型や型クラスを自分で 作ろう (前編) すごいHaskell読書会 in大阪 2週目 #7 2014-07-16 Suguru Hamazaki Making Our Own Types and Type Classes
  • 2.
  • 3.
  • 4.
  • 5.
    data Bool =False | True 型名 値コンスト ラクター 値コンスト ラクター
  • 6.
    data Int =-2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647 実際の定義とは異なるが、 このように考えられる
  • 7.
  • 8.
    • Shape型 • 2次元上の円と長方形を表現する •area関数 • Shape型のデータの面積を求める
  • 9.
    data Shape =Circle Float Float Float | Rectangle Float Float Float Float deriving (Show) 型名 値コンスト ラクター 値コンスト ラクター
  • 10.
    値コンストラクター • 値を作るので、値コンストラクター • 実際には関数の一種 •ex) Circle は Float 型の値を3つ引数として受け取り、Shape 型の値を返す関数 • ex) False は? • 型と値コンストラクターを混同しないよう注意 • 型名と値コンストラクターの名前が同じでもよい Value Constructors
  • 11.
    area :: Shape-> Float area (Circle _ _ r) = pi * r ^ 2 area (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1) 型シグネチャーには、 型名であるShape を使う (Circle や Rectangle は型名ではない) パターンマッチには コンストラクターが使える
  • 12.
    • Point型 • Circle,Rectangle のフィールドを構成する中間的な 型 • nudge関数 • Shape型のデータを移動 • baseCircle, baseRect関数 • ファクトリー的なもの
  • 13.
    data Point =Point Float Float deriving (Show) data Shape = Circle Point Float | Rectangle Point Point deriving (Show) Circle, Rectangle のフィールドを Point 型で定義して整理
  • 14.
    area :: Shape-> Float area (Circle _ r) = pi * r ^ 2 area (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1) パターンマッチも 若干すっきり (?)
  • 15.
    nudge :: Shape-> Float -> Float -> Shape nudge (Circle (Point x y) r) a b = Circle (Point (x + a) (y + b)) r nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1 + a) (y1 + b)) (Point (x2 + a) (y2 + b)) ! baseCircle :: Float -> Shape baseCircle r = Circle (Point 0 0) r ! baseRect :: Float -> Float -> Shape baseRect width height = Rectangle (Point 0 0) (Point width height) baseCircle baseRectangle
  • 16.
    モジュールからエクスポート module Shapes ( Point(..) ,Shape(..) , area , nudge , baseCircle , baseRect ) where 値コンストラクターを 全てエクスポートする記法 明示的に列挙してもよい
  • 17.
  • 18.
  • 19.
    data Person =Person String String Int Float String String deriving (Show) ! firstName :: Person -> String firstName (Person firstname _ _ _ _ _) = firstname ! lastName :: Person -> String lastName (Person _ lastname _ _ _ _) = lastname ! age :: Person -> Int age (Person _ _ age _ _ _) = age ! height :: Person -> Float height (Person _ _ _ height _ _) = height ! phoneNumber :: Person -> String phoneNumber (Person _ _ _ _ number _) = number ! flavor :: Person -> String flavor (Person _ _ _ _ _ flavor) = flavor
  • 20.
    data Person =Person { firstName :: String , lastName :: String , age :: Int , height :: Float , phoneNumber :: String , flavor :: String } deriving (Show) それぞれ、フィールド名とその 型名を指定する ghci> :t firstName firstName :: Person -> String 対応する関数が自動的 に作られる
  • 21.
    data Car =Car { company :: String , model :: String , year :: Int } deriving (Show) ghci> Car { company = "Ford", model = "Mustang", year = 1967} Car {company = "Ford", model = "Mustang", year = 1967} ghci> Car { company = "Ford", year = 1967, model = "Mustang"} Car {company = "Ford", model = "Mustang", year = 1967} フィールドを任意の順 番で指定できる (型クラス Show を derive した場合) 出力 が整形される
  • 22.
    • レコード構文が役に立つケース • フィールドが複数あって、 •どれがどれに対応するのかわかりにくい場合
  • 23.
  • 24.
    data Maybe a= Nothing | Just a 型コンストラクター 型引数
  • 25.
    • 値コンストラクターは、 • 値を引数に取り、 •値を作る • 型コンストラクターは、 • 型を引数に取り (型引数)、 • 型を作る
  • 26.
    • Maybe は型ではなく、型コンストラクター •Maybe Char は型 • Just ‘a’ は Maybe Char 型の値 • Nothing は Maybe a 型の値 • Maybe a は polymorphic な型 • Maybe Int, Maybe Char, etc. として振る舞える
  • 27.
  • 28.
    Carをパラメーター化すると? data Car ab c = Car { company :: a , model :: b , year :: c } deriving (Show) tellCar :: (Show a) => Car String String a -> String tellCar (Car { company = c, model = m, year = y}) = "This " ++ c ++ " " ++ m ++ " was made in " ++ show y tellCar では year の型 しかパラメーター化さ れてない 結局、ほとんどのケースで Car String String Int 型を 使うことになりそう
  • 29.
  • 30.
    データ宣言に型クラス制約は 加えない data (Ord k)=> Map k v = ... このような制約は (文法上は正しいが) 規約上、付けない • 必要な関数の型宣言に付ければ済む • 必要の無い関数の型宣言に付けないで済む
  • 31.
    3次元ベクトルを表わす Vector a 型の場合 data Vector a = Vector a a a deriving (Show) ! vplus :: (Num a) => Vector a -> Vector a -> Vector a (Vector i j k) `vplus` (Vector l m n) = Vector (i + l) (j + m) (k + n) ! dotProd :: (Num a) => Vector a -> Vector a -> a (Vector i j k) `dotProd` (Vector l m n) = i * l + j * m + k * n ! vmult :: (Num a) => Vector a -> a -> Vector a (Vector i j k) `vmult` m = Vector (i * m) (j * m) (k * m) 型クラス制約は 付けない 関数の方に型クラ ス制約を付ける
  • 32.
  • 33.
    型クラス • オブジェクト指向プログラミングにおける「クラス」と混 同しないよう注意 • OOPのクラスは、そのクラスから作られたオブジェクト が持つ性質を規定 •Haskell の型クラスは、型が型クラスのインスタンスにな り、その型の性質を定義する • deriving キーワードを使って、ある型を型クラスのインスタ ンスにすることができる
  • 34.
    data Person =Person { firstName :: String , lastName :: String , age :: Int } deriving (Eq) 1. Person型の値同士を == または /= で比べた時、マッチする値コンスト ラクターを探す 2. 見付かった値コンストラクターのフィールド同士を、それぞれ == また は /= で比べる • 全てのフィールドの型が Eq のインスタンスでなければならない
  • 35.
    mikeD = Person{ firstName = "Michael" , lastName = "Diamond" , age = 43 } ! adRock = Person { firstName = "Adam" , lastName = "Horovitz" , age = 41 } ! mca = Person { firstName = "Adam" , lastName = "Yauch" , age = 44 } ghci> mikeD == adRock False ghci> mikeD == mikeD True ghci> mikeD == Person { firstName = "Michael", lastName = "Diamond", age = 43 } True
  • 36.
    • Eq • equality/ inequality についてテストできる • Show • 値をStringへ変換できる • Read • Stringをパーズして値を作れる • Ord • 大小比較、順序付けできる • Bounded • 上限 (maxBound) 、下限 (minBound) がある • Enum • 順番に列挙することができる
  • 37.
    data Person =Person { firstName :: String , lastName :: String , age :: Int } deriving (Eq, Show, Read) ! mikeD = Person { firstName = "Michael" , lastName = "Diamond" , age = 43 } ! mysteryDude = "Person { firstName = "Michael"" ++ ", lastName = "Diamond"" ++ ", age = 43}" ghci> read mysteryDude :: Person Person {firstName = "Michael", lastName = "Diamond", age = 43} ghci> read mysteryDude == mikeD True String 型を read して Person 型に 型クラス Show の show が使えるので、 値をGHCi コンソールに出力できる
  • 38.
    data Day =Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Ord, Show, Read, Bounded, Enum) !
  • 39.
    λ> Saturday >Sunday False λ> Monday `compare` Wednesday LT λ> minBound :: Day Monday λ> maxBound :: Day Sunday λ> succ Monday Tuesday λ> pred $ succ Monday Monday λ> [Thursday .. Sunday] [Thursday,Friday,Saturday,Sunday] λ> [minBound .. maxBound] :: [Day] [Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday] Ord による大小比較 Bound による上限 と下限 Enum による順次的な 列挙 上限、下限を利用した、 順次的な列挙
  • 40.
  • 41.
    type String =[Char] • 既存の型の別名を定義 • 新しい型が作られるのではない
  • 42.
    type PhoneNumber =String type Name = String type PhoneBook = [(Name,PhoneNumber)] inPhoneBook :: Name -> PhoneNumber -> PhoneBook -> Bool inPhoneBook name pnumber pbook = (name, pnumber) `elem` pbook より明確な意図を伝える 型シグネチャー
  • 43.
    ! ! ! book = [("betty","555-2938") ,("bonnie", "452-2928") ,("patsy", "493-2928") ,("lucille", "205-2928") ,("wendy", "939-8282") ,("penny", "853-2492") ] λ> inPhoneBook "wendy" "939-8282" book True
  • 44.
    型シノニムの型パラメーター化 type AssocList kv = [(k, v)] type IntMap v = Map Int v 型パラメーターを持つ 型シノニムの定義 型パラメーターを 部分適用した型シノニム AssocList, IntMap は 型コンストラクターになる ! 値コンストラクターと 混同しないよう注意
  • 45.
    data Either ab = Left a | Right b deriving (Eq, Ord, Read, Show) • ある型か他のある型の値を表現 • Maybe a と同様に、処理の結果を表わすのに よく使われる • 失敗した場合もデータを保持できるのが違 い
  • 46.
  • 47.
    import qualified Data.Mapas Map ! data LockerState = Taken | Free deriving (Show, Eq) ! type Code = String ! type LockerMap = Map.Map Int (LockerState, Code) ! lockerLookup :: Int -> LockerMap -> Either String Code lockerLookup lockerNumber map = case Map.lookup lockerNumber map of Nothing -> Left $ "Locker " ++ show lockerNumber ++ " doesn't exist!" Just (state, code) -> if state /= Taken then Right code else Left $ "Locker " ++ show lockerNumber ++ " is already taken!" ! lockers :: LockerMap lockers = Map.fromList [(100,(Taken, "ZD39I")) ,(101,(Free,"JAH3I")) ,(103,(Free,"IQSA9")) ,(105,(Free,"QOTSA")) ,(109,(Taken,"893JJ")) ,(110,(Taken,"99292"))]
  • 48.
    練習問題 • 上のような構造を持つ、URIを表現する型を作ってみましょう • queryは複数の key, value のペアを持ちます • 必須の要素とオプショナルの要素があることに注意して下さい https://user:password@example.com:80/path/somewhere?foo=bar#baz scheme userinfo host port path query fragment authority