48
型や型クラスを自分で 作ろう (前編) すごいHaskell読書会 in 大阪 2週目 #7 2014-07-16 Suguru Hamazaki Making Our Own Types and Type Classes

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

Embed Size (px)

Citation preview

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

型や型クラスを自分で作ろう (前編)

すごいHaskell読書会 in 大阪 2週目 #7 2014-07-16

Suguru Hamazaki

Making Our Own Types and Type Classes

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

7章前半の内容データ型の定義方法

レコード構文

型引数 インスタンスの自動導出

型シノニム

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

新しいデータ型を 定義するDefining a New Data Type

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

• 標準ライブラリーでどのように定義されているか?

• Bool

• Int

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

data Bool = False | True

型名 値コンスト

ラクター値コンスト

ラクター

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

data Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647

実際の定義とは異なるが、このように考えられる

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

形づくるShaping Up

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

• Shape型

• 2次元上の円と長方形を表現する

• area関数

• Shape型のデータの面積を求める

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

data Shape = Circle Float Float Float | Rectangle Float Float Float Float

deriving (Show)

型名

値コンスト

ラクター値コンスト

ラクター

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

値コンストラクター• 値を作るので、値コンストラクター

• 実際には関数の一種

• ex) Circle は Float 型の値を3つ引数として受け取り、Shape型の値を返す関数

• ex) False は?

• 型と値コンストラクターを混同しないよう注意

• 型名と値コンストラクターの名前が同じでもよい

Value Constructors

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

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章 (前編)

• Point型

• Circle, Rectangle のフィールドを構成する中間的な型

• nudge関数

• Shape型のデータを移動

• baseCircle, baseRect関数

• ファクトリー的なもの

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

data Point = Point Float Float deriving (Show)

data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

Circle, Rectangle のフィールドを

Point 型で定義して整理

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

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章 (前編)

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章 (前編)

モジュールからエクスポート

module Shapes

( Point(..)

, Shape(..)

, area

, nudge

, baseCircle

, baseRect

) where

値コンストラクターを

全てエクスポートする記法

明示的に列挙してもよい

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

• 型のみエクスポートして、値コンストラクターをエクスポートしないのも OK

• 実装を隠蔽できる

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

レコード構文Record Syntax

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

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章 (前編)

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章 (前編)

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章 (前編)

• レコード構文が役に立つケース

• フィールドが複数あって、

• どれがどれに対応するのかわかりにくい場合

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

型引数Type Parameters

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

data Maybe a = Nothing | Just a

型コンストラクター 型引数

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

• 値コンストラクターは、

• 値を引数に取り、

• 値を作る

• 型コンストラクターは、

• 型を引数に取り (型引数)、

• 型を作る

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

• Maybe は型ではなく、型コンストラクター

• Maybe Char は型

• Just ‘a’ は Maybe Char 型の値

• Nothing は Maybe a 型の値

• Maybe a は polymorphic な型

• Maybe Int, Maybe Char, etc. として振る舞える

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

型引数を取らなくても (0個取っても)

型コンストラクターって言うのかな?

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

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章 (前編)

型引数を使うと良いケース

• 値コンストラクターに含まれる型が、どんなものでも構わないケース

• ex) Maybe a, [a], Data.Map k a

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

データ宣言に型クラス制約は加えない

data (Ord k) => Map k v = ...

このような制約は

(文法上は正しいが)

規約上、付けない

• 必要な関数の型宣言に付ければ済む • 必要の無い関数の型宣言に付けないで済む

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

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章 (前編)

インスタンスの 自動導出Derived Instances

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

型クラス• オブジェクト指向プログラミングにおける「クラス」と混同しないよう注意

• OOPのクラスは、そのクラスから作られたオブジェクトが持つ性質を規定

• Haskell の型クラスは、型が型クラスのインスタンスになり、その型の性質を定義する

• deriving キーワードを使って、ある型を型クラスのインスタンスにすることができる

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

data Person = Person { firstName :: String , lastName :: String , age :: Int } deriving (Eq)

1. Person型の値同士を == または /= で比べた時、マッチする値コンストラクターを探す

2. 見付かった値コンストラクターのフィールド同士を、それぞれ == または /= で比べる

• 全てのフィールドの型が Eq のインスタンスでなければならない

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

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章 (前編)

• Eq

• equality / inequality についてテストできる

• Show

• 値をStringへ変換できる

• Read

• Stringをパーズして値を作れる

• Ord

• 大小比較、順序付けできる

• Bounded

• 上限 (maxBound) 、下限 (minBound) がある

• Enum

• 順番に列挙することができる

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

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章 (前編)

data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Ord, Show, Read, Bounded, Enum)!

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

λ> 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章 (前編)

型シノニムType Synonyms

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

type String = [Char]

• 既存の型の別名を定義 • 新しい型が作られるのではない

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

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章 (前編)

!!!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章 (前編)

型シノニムの型パラメーター化

type AssocList k v = [(k, v)]

type IntMap v = Map Int v

型パラメーターを持つ

型シノニムの定義

型パラメーターを

部分適用した型シノニム

AssocList, IntMap は 型コンストラクターになる !値コンストラクターと 混同しないよう注意

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

data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)

• ある型か他のある型の値を表現 • Maybe a と同様に、処理の結果を表わすのによく使われる • 失敗した場合もデータを保持できるのが違い

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

ロッカーの例

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

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章 (前編)

練習問題

• 上のような構造を持つ、URIを表現する型を作ってみましょう

• query は複数の key, value のペアを持ちます

• 必須の要素とオプショナルの要素があることに注意して下さい

https://user:[email protected]:80/path/somewhere?foo=bar#baz

scheme userinfo host port path query fragment

authority