Download - すごいHaskell読書会 第7章 (前編)
型や型クラスを自分で作ろう (前編)
すごい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 -> Shapenudge (Circle (Point x y) r) a b = Circle (Point (x + a) (y + b)) rnudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1 + a) (y1 + b)) (Point (x2 + a) (y2 + b))!baseCircle :: Float -> ShapebaseCircle r = Circle (Point 0 0) r!baseRect :: Float -> Float -> ShapebaseRect 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 -> StringfirstName (Person firstname _ _ _ _ _) = firstname!lastName :: Person -> StringlastName (Person _ lastname _ _ _ _) = lastname!age :: Person -> Intage (Person _ _ age _ _ _) = age!height :: Person -> Floatheight (Person _ _ _ height _ _) = height!phoneNumber :: Person -> StringphoneNumber (Person _ _ _ _ number _) = number!flavor :: Person -> Stringflavor (Person _ _ _ _ _ flavor) = flavor
data Person = Person { firstName :: String
, lastName :: String
, age :: Int
, height :: Float
, phoneNumber :: String
, flavor :: String } deriving (Show)
それぞれ、フィールド名と
その
型名を指定する
ghci> :t firstNamefirstName :: 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 == adRockFalseghci> mikeD == mikeDTrueghci> 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 :: PersonPerson {firstName = "Michael", lastName = "Diamond", age = 43}ghci> read mysteryDude == mikeDTrue
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" bookTrue
型シノニムの型パラメーター化
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 CodelockerLookup 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 :: LockerMaplockers = 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:[email protected]:80/path/somewhere?foo=bar#baz
scheme userinfo host port path query fragment
authority