5. 7.2 形づくる
• 図形(Shape)の面積を求める関数を定義
area :: Shape -> Float
area ( Circle _ _ r ) = pi * r ^ 2
area ( Rectangle x1 y1 x2 y2 ) =
(abs $ x2-x1) * (abs $ y2 - y1)
area関数はShape型を受け取り、その面積(Float)を返す関数
引数のShape型を受け取る部分でパターンマッチを行い
型ごとに適切な実装を行っている
(この関数は任意のShape型の値を引数として受け取れる)
6. 7.2 形づくる
• ghci上で次のコードを実行する
data Shape = Circle Float Float Float
| Rectangle Float Float Float Float
Circle 10 20 5
<interactive>:19:1:
No instance for (Show Shape)
arising from a use of `print'
Possible fix: add an instance declaration for (Show Shape)
In a stmt of an interactive GHCi command: print it
Shapeというデータ型の値を文字列として表示する方法が分かりません
(ghci上で実行した場合、その値の文字列表現を結果として表示する)
7. 7.2 形づくる
• Shape型の定義に次の一文を追加
data Shape = Circle Float Float Float
| Rectangle Float Float Float Float
deriving (Show)
Circle 10 20 5
deriving (Show) をつけることで、
Shapeデータ型の各値を文字列でどのように表現するかを
自動的に実装してくれる(詳しくは後述)
8. 7.2 形づくる
• ところで・・・
data Shape = Circle Float Float Float
| Rectangle Float Float Float Float
deriving (Show)
Circle 10 20 5
Circleの10, 20, 5というのは何を表している?
Circle [中心のX座標] [中心のY座標] [円の半径] を想定
しているが、この順番を間違えてしまうかも。
Shapeというデータ型の値を文字列として表示する方法が分かりません
(ghci上で実行した場合、その値の文字列表現を結果として表示する)
9. 7.2 形づくる
• Point型を定義
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float
| Rectangle Point Point deriving (Show)
Circle (Point 10 20) 5
Circle [中心座標] [円の半径] ということを型で表現できる
データ型を表現するために
新たなデータ型を定義して使用することができる
10. 7.2 形づくる
• 図形(Shape)の面積を求める関数を修正
area :: Shape -> Float
area ( Circle _ r ) = pi * r ^ 2
area ( Rectangle (Point x1 y1) (Point x2 y2) ) =
(abs $ x2-x1) * (abs $ y2 - y1)
area関数はShape型を受け取り、その面積(Float)を返す関数(修正版)
データ型の中のデータ型もパターンマッチの対象にできる。
12. 7.3 レコード構文
• 名前付きのフィールドを備えたデータ型の作成
data Person = Person {
firstName :: String
, lastName :: String
, age :: Int
, height :: Float
, phoneNumber :: String
, flavor :: String } deriving (Show)
フィールド名 :: フィールドの型
作成したデータ型のフィールドに
それぞれ名前を持たせたデータ型を作成できる
13. 7.3 レコード構文
• 値の作成と自動生成される関数
-- フィールド名の関数(データ型 -> フィールド)を自動生成する
ghci> :t flavor
flavor :: Person -> String
-- レコード型の値を生成する(カンマ区切りでフィールドに値を設定)
ghci> let p = Person { firstName = “Buddy”,
lastName = “Finklestein”, age = 43, height = 184.2,
phoneNumber = “526-2928”, flavor = “Chocolate” }
レコード型のパラメータを名前付きで全て指定する(順序は自由)
モジュールで値コンストラクタの指定をしなくてもレコード型の値を生成できる
Q. レコード型の値をエクスポートするときにそれを公開しないようにできる?
14. 7.4 型引数
• 値コンストラクタと型コンストラクタ
data Point = Point Float Float deriving (Show)
data Maybe a = Nothing | Just a
このaが型引数
ghci> Point 5 10 (型を与えることで使用する具体型が決定される)
ghci> Point “x” “y” -- ERROR
ghci> Just 10 -- (Num a) => Maybe a
ghci> Just “string” -- Maybe [Char]
値コンストラクタ:値を受け取って新しい値(Ex. Point 5.0 10.0)を作る
型コンストラクタ:型を受け取って新しい型(Ex. Maybe Int)を作る
(この例では単なるMaybeという型の値は存在できないが
Maybe Intという型の値は存在できる)
15. 7.4 型引数
• 様々な型を格納するデータ型が作れる
data Maybe a = Nothing | Just a
-- もしも型引数がなければ...
data IntMaybe = INothing | IJust Int
data StrMaybe = SNothing | SJust String
ghci> Just 10 :: Maybe Int
ghci> Just “string” :: Maybe String
異なるデータ型ごとにデータを作る必要がなくなる
21. 7.6 型シノニム
• 既存データ型に別の名前を与える
type String = [Char]
type IntMaybe = Maybe Int
type Pair a b = (a, b)
type MyStr = String
testStr :: MyStr -> String
testStr s = s
コンパイルエラー
ghci> Pair 1 2
ghci> testStr (“abc” :: [Char])
別の名前を与えるだけの機能であり
値コンストラクタを作ってくれるわけではない
別名であるので、同じものを指すのであれば関数の引数に引き渡せる
22. 7.6 型シノニム
• 型引数を2つ取る型の例
data Either a b = Left a | Right b
deriving (Eq, Ord, Read, Show)
ghci> :t Left True
Left True :: Either True b
ghci> :t Right ‘a’
Right ‘a’ :: Either a Char
Leftは後の型引数bを多相のまま残して失敗を表し
Rightは先の型引数aを多相のまま残して成功を表す
Q. なぜLeftを失敗に使い、Rightを成功に使うのか?(Rightだから?)
23. 7.6 型シノニム
• 失敗した時になぜ失敗したのかを返せる
on1to10 :: Int -> Either Ordering Int
on1to10 x
| x < 1 = Left LT -- value less than [1,10]
| x > 10 = Left GT -- value greater than [1,10]
| otherwise = Right x -- value on [1,10]
整数が範囲内であるかどうかを判定する関数on1to10
範囲外である場合に、引数が小さいor大きいという
型情報Orderingの値を失敗値Leftと一緒に返す
24. 7.7 再帰的なデータ構造
• フィールドに自分自身の型を持つデータ型
data List’ a = Empty | Cons a (List’ a)
deriving (Show, Read, Eq, Ord)
ghci> Empty
Empty
ghci> 4 `Cons` (5 `Cons` Empty)
Cons 4 (Cons 5 Empty)
ghci> :t Cons
Cons :: a -> List' a -> List' a
List’はEmpty、もしくはCons値コンストラクタで作成された値
ConsコンストラクタはaとList’ aを取ってList’ aの値を作成する
25. 7.7 再帰的なデータ構造
• イメージでつかもう
data List’ a = Empty | Cons a (List’ a)
deriving (Show, Read, Eq, Ord)
List’
Empty
| Cons a (List’ a)
1 : 2 : 3 : []
1 `Cons` 2 `Cons` 3 `Cons` Empty
26. 7.7 再帰的なデータ構造
• 中置記号で書き直す コロン(:)から始まる記号のみの値コ
ンストラクタは中置関数になる
infixr 5 :-:
data List’ a = Empty | a :-: (List’ a)
deriving (Show, Read, Eq, Ord)
ghci> 1 :-: 2 :-: Empty
ghci> (1 :-: ( 2 :-: Empty))
infixl or infixrで右結合か左結合かを指定する(省略すると右結合)
また、結合性の優先順位を指定する(数字が大きいほど優先して結合)
省略した場合はinfixl 9が指定されていることと同じ
右結合を指定しない場合は下のように括弧をつける必要がある
28. 7.7 再帰的なデータ構造
• 木構造(2分探索木)
data Tree a =
EmptyTree | Node a (Tree a) (Tree a)
deriving (Show)
Tree
EmptyTree Node a (Tree a) (Tree b)
Nodeは値aと左の木、右の木を持つ
29. 7.7 再帰的なデータ構造
Tree
EmptyTree Node a (Tree a) (Tree b)
P.139に描かれている木の例
5
3 7
1 4 6 8
30. 7.7 再帰的なデータ構造
• 木構造(2分[探索]木)
data Tree a =
EmptyTree | Node a (Tree a) (Tree a)
deriving (Show)
この定義だけでは、ただの2分木を表現しているに過ぎない
そのため、上記のデータ型では2分探索木では無いTreeも表現できる
Q. 型引数に型クラス制約をつけないというルールはあったが、
データ構造上の制約をデータ型の定義の段階で入れることはあるのか?
31. 7.7 再帰的なデータ構造
• 2分探索木を作成するための関数を定義
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 v left right)
| x == v = Node v left right
| x < v = Node v (treeInsert x left) right
| x > v = Node v left (treeInsert x right)
木が空なら、新たなノードを作成する
木に値がある場合、挿入する値が小さければ左へ、値が大きければ右へ挿入する
32. 7.7 再帰的なデータ構造
• 2分探索木に特定の値があるかを探索する関数
treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem :: x EmptyTree = False
treeElem :: x (Node v left right)
| x == v = True
| x < v = treeElem x left
| x > v = treeElem x right
第2引数のTree aが2分探索木であれば正しい答えを返す
Q. このような時にInvalidな値を渡された場合
Haskellではどのように対処するのか?
33. 7.7 再帰的なデータ構造
• 2分探索木の作成
let nums = [8,6,4,1,7,3,5]
let tree = foldr treeInsert EmptyTree nums
*Main> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
foldrにはa,bの2匹数を受け取りbを返す関数(第1引数)と
その関数に与える初期値(第2引数)
および関数に順に与える値のリスト(第3引数)を渡す
ここでは、まずtreeInsert 5 EmptyTreeが行われ
その戻り値(Node 5 EmptyTree EmptyTree)に対して
treeInsert 3 (Node 5 EmptyTree EmptyTree) が行われ
...と続いていき最終的にP.139の2分探索木を最終的に得る
35. 7.8 型クラス中級講座
• Eq型クラスの実装 Eq型クラスの定義の始まり
a: Eqのインスタンスになる型を表す
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
Eq型クラスのインスタンスであれば
x == y = not (x /= y) 使用できる関数 ( == と /= )
x /= y = not (x == y)
型クラス関数のデフォルト実装
(省略可能)
ある型をEq型クラスのインスタンスにしようとする場合
定義されている全ての関数を実装する必要がある
Eq型クラスの場合はデフォルト実装が相互再帰しているので
どちらか一方を書き換えるだけでよい
36. 7.8 型クラス中級講座
• Eq型クラスのインスタンスの実装
data TrafficLight = Red | Yellow | Green
instance Eq TrafficLight where
Red == Red = True
Yellow == Yellow = True
Green == Green = True
_ == _ = False Eq型クラスの関数(==)の実装
-- x /= y = not (x == y) (パターンマッチを利用)
関数(/=)は関数(==)を用いて定義されているので
関数(==)の実装を上書きすることでEq型クラスで
必要とされる関数の実装を行うことができる
(型クラスの最小完全性定義)
37. 7.8 型クラス中級講座
• 型クラスのサブクラス
class (Eq a) => Num a where
...
2つのインスタンス関係のイメージ
Eq型クラスの
インスタンス
Num型クラスの
インスタンス
型クラスNumのインスタンスとなる型は
型クラスEqのインスタンスである必要がある
38. 7.8 型クラス中級講座
• 多層型(Maybe)のインスタンス
instance (Eq m) => Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False
Eq型クラスのインスタンスである型mを
持つ(Maybe m)の具体型に対して
Eq型クラスのインスタンスを定義する
(これはmに対して==を使用しているため)
逐一型ごとに(Maybe Int, Maybe Char, ...)インスタンスを実装せずとも
型を単に変数と記述してインスタンスを実装することが許されている
39. 7.8 型クラス中級講座
• 型クラスについての情報
ghci> :info Num
class Num a where
(+) :: a -> a -> a
(*) :: a -> a -> a
(-) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
-- Defined in `GHC.Num'
instance Num Integer -- Defined in `GHC.Num'
instance Num Int -- Defined in `GHC.Num'
instance Num Float -- Defined in `GHC.Float'
instance Num Double -- Defined in `GHC.Float'
:info [型クラス名]で、型クラスの性質を示す関数と
実装されているインスタンス一覧を表示することができる
40. 7.9 YesとNoの型クラス
• 型クラスを自作する
class YesNo a where
yesno :: a -> Bool
• 自作の型クラスを独自実装する(例: Maybe)
instance (YesNo m) => YesNo (Maybe m) where
yesno (Just x) = yesno x
yesno _ = False
C言語やJavaScriptの条件判定がtrueとなる
(ifの中に記述するとtrueと判断されるもの)を表現する型クラス
使用する時は型指定が必要なこともある
(yesno 0だけではIntと推論してくれなかった)
41. 7.9 YesとNoの型クラス
• If式(っぽいもの)を自作する
yesnoIf :: (YesNo y) => y -> a -> a -> a
yesnoIf yesnoVal tValue fValue
| yesno yesnoVal = tValue
| otherwise = fValue
Haskellでは遅延評価であるため
tValue, fValueの値が必要になるまで評価されない
一方で正格評価な言語では
この関数が実行される段階でtValue, fValueともに
評価が終わっていなければならない
42. 7.10 Functor型クラス
• 全体を写せる(map over)ものの型クラス
class Functor something where
fmap (a -> b) -> something a -> something b
1, 2, 3, ..., 26
型クラス
型クラス Int -> Char something Char
something Int
‘A’, ‘B’, ‘C’, ..., ‘Z’
ある型がFunctorであるならば、その型に対するfmapが提供されている
fと書くと単なる関数のように見えたのでsomethingで書き直してある
43. 7.10 Functor型クラス
• Maybe, List, TreeなどはFunctor型クラスのインスタンス
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
class Eq a where class Functor something where
(==) :: a -> a -> Bool fmap (a -> b) ->
(/=) :: a -> a -> Bool something a -> something b
Functor型クラスのインスタンスとして指定する型は
型を1つ取る型コンストラクタを持つ必要がある
(Functorは具体型では無く、型コンストラクタを要求している)
44. 7.10 Functor型クラス
• 1つの型コンストラクタを持てば良いのでEitherでもOK
instance Functor (Either a) where
fmap f (Right rx) = Right $ f rx
fmap f (Left lx) = Left lx Either aのaは型変数
任意のaに対してのインスタ
ンスを実装
ghci> fmap (*2) (Right 10)
Right 20
ghci> fmap (*2) (Left “ABC”)
Left “ABC”
fmapの第1匹数はRightの値の型bを別の型に変換する関数
Leftに使われる型aに対しては写像関数fの対象ではない
45. 7.11 型を司るもの、種類
• :kコマンドで型の種類を知ることができる
ghci> :k Int
Int :: *
ghci> :k Maybe
Maybe :: * -> *
ghci> :k Maybe Int
Maybe Int :: *
ghci> :k Either String
Either String :: * -> *
*は具体型 (Intなど)
* -> *は具体型を1つ取って具体型を返す型コンストラクタ (Maybeなど)
* -> * -> *は具体型を2つ取って具体型を返す型コンストラクタ (Eitherなど)
なお、部分適用を行うことも可能
47. 演習問題2
• 型クラスMyStackを次のように定義する
class MyStack s where
push :: a -> s a -> s a
pop :: s a -> Maybe (a, s a)
peek :: s a -> Maybe a
この時、標準で提供されるリストをMyStackのインスタンス
として定義せよ
instance MyStack [] where
(以下を実装する)
48. 演習問題1: 解答例
getMaxDepth :: Tree a -> Int
getMaxDepth EmptyTree = 0
getMaxDepth (Node _ left right) =
1 + max (getMaxDepth left) (getMaxDepth right)
左と右の木の深い方の木に、1を加えたものが自分の深さ
getMaxNode :: (Ord a) => Tree a -> Maybe a
getMaxNode EmptyTree = Nothing
getMaxNode (Node x _ EmptyTree) = Just x
getMaxNode (Node _ _ right) = getMaxNode right
2分探索木なので、最も右側にあるノードが最大の値を保持する
49. 演習問題2: 解答例
instance MyStack [] where
push x xs = x:xs
pop [] = Nothing
pop (x:xs) = Just (x, xs)
peek [] = Nothing
peek (x:_) = Just x
• push: 要素を先頭に追加したリストを返す
• pop: 要素があれば、先頭から取り出した値と先頭を取り除
いたリストを返す
• peek: 要素があれば、先頭の値を返す
50. 演習問題2: 動作例
ghci> let stack = foldr push [] [1,2,3,4,5]
ghci> stack
[1,2,3,4,5]
ghci> peek stack
Just 1
ghci> pop stack
Just (1,[2,3,4,5])
ghci> pop []
Nothing