すごいhaskell読書会 第7章 (前編)
TRANSCRIPT
![Page 1: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/1.jpg)
型や型クラスを自分で作ろう (前編)
すごいHaskell読書会 in 大阪 2週目 #7 2014-07-16
Suguru Hamazaki
Making Our Own Types and Type Classes
![Page 2: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/2.jpg)
7章前半の内容データ型の定義方法
レコード構文
型引数 インスタンスの自動導出
型シノニム
![Page 3: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/3.jpg)
新しいデータ型を 定義するDefining a New Data Type
![Page 4: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/4.jpg)
• 標準ライブラリーでどのように定義されているか?
• Bool
• Int
![Page 5: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/5.jpg)
data Bool = False | True
型名 値コンスト
ラクター値コンスト
ラクター
![Page 6: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/6.jpg)
data Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647
実際の定義とは異なるが、このように考えられる
![Page 7: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/7.jpg)
形づくるShaping Up
![Page 8: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/8.jpg)
• Shape型
• 2次元上の円と長方形を表現する
• area関数
• Shape型のデータの面積を求める
![Page 9: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/9.jpg)
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
deriving (Show)
型名
値コンスト
ラクター値コンスト
ラクター
![Page 10: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/10.jpg)
値コンストラクター• 値を作るので、値コンストラクター
• 実際には関数の一種
• ex) Circle は Float 型の値を3つ引数として受け取り、Shape型の値を返す関数
• ex) False は?
• 型と値コンストラクターを混同しないよう注意
• 型名と値コンストラクターの名前が同じでもよい
Value Constructors
![Page 11: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/11.jpg)
area :: Shape -> Float
area (Circle _ _ r) = pi * r ^ 2
area (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
型シグネチャーには、
型名であるShape を使う
(Circle や Rectangle は型名ではない)
パターンマッチには
コンストラクターが使える
![Page 12: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/12.jpg)
• Point型
• Circle, Rectangle のフィールドを構成する中間的な型
• nudge関数
• Shape型のデータを移動
• baseCircle, baseRect関数
• ファクトリー的なもの
![Page 13: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/13.jpg)
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
Circle, Rectangle のフィールドを
Point 型で定義して整理
![Page 14: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/14.jpg)
area :: Shape -> Float
area (Circle _ r) = pi * r ^ 2
area (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
パターンマッチも
若干すっきり (?)
![Page 15: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/15.jpg)
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
![Page 16: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/16.jpg)
モジュールからエクスポート
module Shapes
( Point(..)
, Shape(..)
, area
, nudge
, baseCircle
, baseRect
) where
値コンストラクターを
全てエクスポートする記法
明示的に列挙してもよい
![Page 17: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/17.jpg)
• 型のみエクスポートして、値コンストラクターをエクスポートしないのも OK
• 実装を隠蔽できる
![Page 18: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/18.jpg)
レコード構文Record Syntax
![Page 19: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/19.jpg)
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
![Page 20: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/20.jpg)
data Person = Person { firstName :: String
, lastName :: String
, age :: Int
, height :: Float
, phoneNumber :: String
, flavor :: String } deriving (Show)
それぞれ、フィールド名と
その
型名を指定する
ghci> :t firstNamefirstName :: Person -> String 対応する関数
が自動的
に作られる
![Page 21: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/21.jpg)
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 した場合) 出力
が整形される
![Page 22: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/22.jpg)
• レコード構文が役に立つケース
• フィールドが複数あって、
• どれがどれに対応するのかわかりにくい場合
![Page 23: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/23.jpg)
型引数Type Parameters
![Page 24: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/24.jpg)
data Maybe a = Nothing | Just a
型コンストラクター 型引数
![Page 25: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/25.jpg)
• 値コンストラクターは、
• 値を引数に取り、
• 値を作る
• 型コンストラクターは、
• 型を引数に取り (型引数)、
• 型を作る
![Page 26: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/26.jpg)
• Maybe は型ではなく、型コンストラクター
• Maybe Char は型
• Just ‘a’ は Maybe Char 型の値
• Nothing は Maybe a 型の値
• Maybe a は polymorphic な型
• Maybe Int, Maybe Char, etc. として振る舞える
![Page 27: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/27.jpg)
型引数を取らなくても (0個取っても)
型コンストラクターって言うのかな?
![Page 28: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/28.jpg)
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 型を 使うことになりそう
![Page 29: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/29.jpg)
型引数を使うと良いケース
• 値コンストラクターに含まれる型が、どんなものでも構わないケース
• ex) Maybe a, [a], Data.Map k a
![Page 30: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/30.jpg)
データ宣言に型クラス制約は加えない
data (Ord k) => Map k v = ...
このような制約は
(文法上は正しいが)
規約上、付けない
• 必要な関数の型宣言に付ければ済む • 必要の無い関数の型宣言に付けないで済む
![Page 31: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/31.jpg)
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)
型クラス制約は
付けない
関数の方に型クラ
ス制約を付ける
![Page 32: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/32.jpg)
インスタンスの 自動導出Derived Instances
![Page 33: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/33.jpg)
型クラス• オブジェクト指向プログラミングにおける「クラス」と混同しないよう注意
• OOPのクラスは、そのクラスから作られたオブジェクトが持つ性質を規定
• Haskell の型クラスは、型が型クラスのインスタンスになり、その型の性質を定義する
• deriving キーワードを使って、ある型を型クラスのインスタンスにすることができる
![Page 34: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/34.jpg)
data Person = Person { firstName :: String , lastName :: String , age :: Int } deriving (Eq)
1. Person型の値同士を == または /= で比べた時、マッチする値コンストラクターを探す
2. 見付かった値コンストラクターのフィールド同士を、それぞれ == または /= で比べる
• 全てのフィールドの型が Eq のインスタンスでなければならない
![Page 35: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/35.jpg)
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
![Page 36: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/36.jpg)
• Eq
• equality / inequality についてテストできる
• Show
• 値をStringへ変換できる
• Read
• Stringをパーズして値を作れる
• Ord
• 大小比較、順序付けできる
• Bounded
• 上限 (maxBound) 、下限 (minBound) がある
• Enum
• 順番に列挙することができる
![Page 37: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/37.jpg)
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 コンソールに出力できる
![Page 38: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/38.jpg)
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Ord, Show, Read, Bounded, Enum)!
![Page 39: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/39.jpg)
λ> 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 による順次的な
列挙
上限、下限を利用した、
順次的な列挙
![Page 40: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/40.jpg)
型シノニムType Synonyms
![Page 41: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/41.jpg)
type String = [Char]
• 既存の型の別名を定義 • 新しい型が作られるのではない
![Page 42: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/42.jpg)
type PhoneNumber = String
type Name = String
type PhoneBook = [(Name,PhoneNumber)]
inPhoneBook :: Name -> PhoneNumber -> PhoneBook -> Bool
inPhoneBook name pnumber pbook = (name, pnumber) `elem` pbook
より明確な意図を伝える
型シグネチャー
![Page 43: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/43.jpg)
!!!book = [("betty", "555-2938") ,("bonnie", "452-2928") ,("patsy", "493-2928") ,("lucille", "205-2928") ,("wendy", "939-8282") ,("penny", "853-2492") ]
λ> inPhoneBook "wendy" "939-8282" bookTrue
![Page 44: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/44.jpg)
型シノニムの型パラメーター化
type AssocList k v = [(k, v)]
type IntMap v = Map Int v
型パラメーターを持つ
型シノニムの定義
型パラメーターを
部分適用した型シノニム
AssocList, IntMap は 型コンストラクターになる !値コンストラクターと 混同しないよう注意
![Page 45: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/45.jpg)
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
• ある型か他のある型の値を表現 • Maybe a と同様に、処理の結果を表わすのによく使われる • 失敗した場合もデータを保持できるのが違い
![Page 46: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/46.jpg)
ロッカーの例
![Page 47: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/47.jpg)
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"))]
![Page 48: すごいHaskell読書会 第7章 (前編)](https://reader030.vdocuments.net/reader030/viewer/2022020101/55a932081a28ab35368b4655/html5/thumbnails/48.jpg)
練習問題
• 上のような構造を持つ、URIを表現する型を作ってみましょう
• query は複数の key, value のペアを持ちます
• 必須の要素とオプショナルの要素があることに注意して下さい
https://user:[email protected]:80/path/somewhere?foo=bar#baz
scheme userinfo host port path query fragment
authority