Upload
shinta-hatatani
View
1.805
Download
8
Embed Size (px)
Citation preview
12章 モノイド
HATATANI Shinta(@apstndb)
November 18, 2012
1 / 59
自己紹介
I @apstndbI 千葉工業大学@津田沼 修士課程I 好きな言語は C++
I BoostCon 2011によると「Haskellは C++ TMPのための擬似言語]
I http://boostcon.boost.org/program/sessions#milewski-haskell-the-pseudocode-language-for-c-template-metaprogramming
I 乗るしかないこのビッグウェーブI 目標は光と闇が両方そなわり最強に見える感じ
2 / 59
Section 1
12.1 既存の型を新しい型にくるむ
3 / 59
11章のおさらい
リストをアプリカティブファンクタにする方法は複数
1. 左辺のリストの関数と右辺のリストの値の全組み合わせ
ghci> [(+1),(*100),(*5)] <*> [1,2,3][2,3,4,100,200,300,5,10,15]
2. 左辺の関数を同じ位置にある右辺の値に適用既にリストはアプリカティブなので,区別するために ZipLista型 (Control.Applicative)を導入
ghci> getZipList $ ZipList [(+1),(*100),(*5)]<*> ZipList [1,2,3]
[2,200,15]
4 / 59
ZipListはどう実装する?
別の型を作る必要がある
1. dataを使う
data ZipList a = ZipList [a]
値を取り出すにはパターンマッチを使う
2. dataのレコード構文を使う
data ZipList a = ZipList { getZipList :: [a] }
リストを取り出す getZipList関数が手に入る
ある型の型クラスにするには derivingと instanceを使う
5 / 59
newtype
3. newtypeを使う
newtypeは「1つの型を取り,それを何かにくるんで別の型に見せかける」ことに特化
実際の ZipList aの定義
newtype ZipList a = ZipList { getZipList :: [a] }
Q dataの代わりに newtypeにしただけだけど何が良くなるの?
A dataはコンストラクタに包む時も解く時もランタイムのオーバーヘッドがある
newtypeはコンパイル時のみ別の型として扱われ,ランタイムにオーバーヘッドが無い
6 / 59
newtype 2
Q 常に dataの代わりに newtypeを使うのは?A newtypeは値コンストラクタもフィールドも 1つ
dataは値コンストラクタもフィールドも複数可
data Profession = Fighter | Archer | Accountantdata Race = Human | Elf | Orc | Goblindata PlayerCharacter = PlayerCharacter Race Profession
7 / 59
newtypeで deriving
I 可能なのは Eq, Ord, Enum, Bounded, Show, ReadI GHC拡張 GeneralizedNewtypeDerivingで任意の型クラス
I 包む型が既にそのインスタンスである
例えば
newtype CharList = CharList { getCharList :: [Char] }deriving (Eq, Show)
ghci> CharList "this will be shown!"CharList {getCharList = "this will be shown!"}ghci> CharList "benny" == CharList "benny"Trueghci> CharList "benny" == CharList "oisters"False
8 / 59
コンストラクタの型
CharList :: [Char] -> CharList
[Char](文字列)を受け取って CharListを返す
getCharList :: CharList -> [Char]
CharListを受け取って [Char](文字列)を返す
解釈
1. 包んだりほどいたり2. 2種類の型間の変換
9 / 59
newtype を使って型クラスのインスタンスを作る (p.260)
型引数が一致しなくて型クラスのインスタンスにできない場合が
ある
Maybeを Functorにするのは簡単
class Functor f wherefmap :: (a -> b) -> f a -> f b
に対して f = Maybeで
instance Functor Maybe where
とし,あとは fmapを実装するだけ
10 / 59
タプルを Functorに
Q タプルを Functorにするには?例えば,fmapが第一要素に対して働くようにしたい(fmap (+3) (1, 1)が (4, 1)になる)タプルのままだと型 (a, b)の aを fmapが変更することを表すのは難しい.
A タプルを newtypeして,2つの型引数の順番を入れ替えることで Functorのインスタンスにできる
newtype Pair b a = Pair { getPair :: (a, b) }
instance Functor (Pair c) wherefmap f (Pair (x, y)) = Pair (f x, y)
newtypeで作った型にはパターンマッチも使える取り出したタプルの第一要素 (x)に fを適用してから Pair x y型に再変換
11 / 59
この時 fmapの型は
fmap :: (a -> b) -> Pair c a -> Pair c b
class Functor f wherefmap :: (a -> b) -> f a -> f b
の f = Pair cとなる
ghci> getPair $ fmap (*100) (Pair (2, 3))(200,3)ghci> getPair $ fmap reverse (Pair ("london calling", 3))("gnillac nodnol",3)
12 / 59
newtypeと遅延評価 (p.261)newtypeは既存の型を新しい型に変えるだけ
これらをHaskellは型としては区別するが,同一の内部表現を使う
newtypeは dataより高速なだけでなくパターンマッチがより怠惰になる
Haskellはデフォルトが遅延評価
undefinedを評価するとアウト
ghci> undefined*** Exception: Prelude.undefined
評価しなければセーフ
ghci> head [3, 4, 5, undefined, 2, undefined]3
13 / 59
例えば dataに対してパターンマッチをする関数を作る
data CoolBool = CoolBool { getCoolBool :: Bool }helloMe :: CoolBool -> StringhelloMe (CoolBool _) = "hello"
ghci> helloMe undefined"*** Exception: Prelude.undefined
dataで定義した型には複数の値コンストラクタがありうる
I (CoolBool _)にマッチするか確認できるところまで評価が必要
I undefinedを評価してしまう
14 / 59
dataではなく newtypeを使ったら?newtype CoolBool = CoolBool { getCoolBool :: Bool }helloMe :: CoolBool -> StringhelloMe (CoolBool _) = "hello" -- 変更なし
ghci> helloMe undefined"hello"
こちらは動く!
newtypeを使った場合はコンストラクタが 1つだけだと Haskellが知っているので,評価をする必要がない
dataと newtypeは似ているが異なったメカニズム
I data オリジナルな型を無から作り出すI パターンマッチは箱から中身を取り出す操作
I newtype 既存の型をもとに,区別される新しい型を作るI パターンマッチはある型を別の方に直接変換する操作
15 / 59
type vs. newtype vs. data(p.263)
type 既存の型に型シノニム (別名)を与える
type IntList = [Int]
[Int]型に IntListという別名を付けただけで,値コンストラクタなど使わなくても自由に交換可能
ghci> ([1,2,3] :: IntList) ++ ([1,2,3] :: [Int])[1,2,3,1,2,3]
主に複雑な型に名前をつけて目的を分かりやすくするために使用
16 / 59
newtype 既存の型から新しい型を作る
型クラスのインスタンスを作るために使用
newtype CharList = CharList { getCharList :: [Char] }
I CharListと [Char]を++で連結することは不可I 2つの CharListを++で連結することも不可
I CharListはリストを含んでもリストではない!
I 値コンストラクタ名とフィールド名が型の相互変換関数
I 型クラスは引き継がない.derivingするか宣言するI 値コンストラクタもフィールドも 1つの dataは newtypeに
data 自作の新しいデータ型を作る
I フィールドとコンストラクタを複数持つデータ型を作れる
I リスト, Maybe, 木, etc…17 / 59
12.1のまとめ
I 型シノニム (type)I 型シグネチャの整理や型名が体を表すようにしたい
I newtypeI 既存の方をある型クラスのインスタンスにしたい
I dataI 何かまったく新しいものを作りたい
18 / 59
Section 2
12.2 Monoid大集合 (p.265)
19 / 59
型クラスは同じ振る舞いをする型のインターフェース
I Eq 等号が使えるI Ord 順序が付けられるI Functor, Applicative …
新しい型を作る時は欲しい機能の型クラスを実装する
20 / 59
こんな型はどうだろう?
I *は 2つの数を取って掛け算する関数I 1 * xも x * 1も x
ghci> 4 * 14ghci> 1 * 99
I ++は二つのリストを連結する関数I x ++ []も [] ++ xも x
ghci> [1,2,3] ++ [][1,2,3]ghci> [] ++ [0.5, 2.5][0.5,2.5]
21 / 59
共通の性質
I 関数の引数は 2つI 2つの引数と返り値の型は同じ
I 相手を変えない特殊な値 (単位元)が存在するI 3つ以上の値をまとめる時,計算する順序を変えても同じ結果 (結合的: associativity)
ghci> (3 * 2) * (8 * 5)240ghci> 3 * (2 * (8 * 5))240ghci> "la" ++ ("di" ++ "ga")"ladiga"ghci> ("la" ++ "di") ++ "ga""ladiga"
この性質を持つものこそがモノイド!
22 / 59
Monoid型クラス (p.266)Data.Monoidに定義されている Monoidの定義
class Monoid m wheremempty :: mmappend :: m -> m -> mmconcat :: [m] -> mmconcat = foldr mappend mempty
I インスタンスは具体型だけ (mは型引数を取らない)I Functorや Applicativeとは違う
I memptyは単位元I mappendは固有の 2項演算
I 名前は appendとついているが,2つの値から第 3の値を返す関数
I mconcatはモノイドのリストからmappendで 1つの値を計算する関数
I デフォルトは memptyを初期値にした右畳み込み
実装する必要があるのはmemptyとmappend 23 / 59
モノイド則 (p.267)
モノイドが満たすべき法則
I 単位元
I mempty `mappend` x = xI x `mappend` mempty = x
I 結合的
I (x `mappend` y) `mappend` z = x `mappend` (y`mappend` z)
満たしているかを Haskellは強制しないので,実際に法則を満たすのはプログラマの責任
蛇足 mappendは GHC 7.4から<>という別名ができた
I fmapに対する<$>
24 / 59
12.2のまとめ
モノイドは
I 同じ型の 2つの値からその型の 1つの値を作る 2項演算I モノイド則を守る必要がある
I 単位元I 結合的
25 / 59
Section 3
12.3 モノイドとの遭遇 (p.268)
26 / 59
リストはモノイド (p.268)
instance Monoid [a] wheremempty = []mappend = (++)
リストは中身の型に関わらずMonoid
27 / 59
モノイドとしてリストを使う
ghci> [1,2,3] `mappend` [4,5,6][1,2,3,4,5,6]ghci> ("one" `mappend` "two") `mappend` "tree""onetwotree"ghci> "one" `mappend` ("two" `mappend` "tree")"onetwotree"ghci> "one" `mappend` "two" `mappend` "tree""onetwotree"ghci> "pang" `mappend` mempty"pang"ghci> mconcat [[1, 2], [3, 6], [9]] -- = concat[1,2,3,6,9]ghci> mempty :: [a] -- リストだと分かるように型注釈[]
28 / 59
モノイド則に交換法則はない
モノイド則は a `mappend` b = b `mappend` aを要求しない
交換法則は*は満たすが殆どのモノイドは満たさない!
ghci> "one" `mappend` "two""onetwo"ghci> "two" `mappend` "one""twoone"
29 / 59
Productと Sum(p.269)
数をモノイドにする方法は 2つある
I *を演算にして 1を単位元にするI +を演算にして 0を単位元にする
ghci> 0 + 44ghci> 5 + 05ghci> (1 + 3) + 59ghci> 1 + (3 + 5)9
2つの方法を使い分けるために newtypeがある
30 / 59
Productの定義とインスタンス宣言 in Data.Monoid
newtype Product a = Product { getProduct :: a }deriving (Eq, Ord, Read, Show, Bounded)
instance Num a => Monoid (Product a) wheremempty = Product 1Product x `mappend` Product y = Product (x * y)
31 / 59
Productを使ってみる
全ての Numのインスタンス aに対して Productが使える
ghci> getProduct $ Product 3 `mappend` Product 927ghci> getProduct $ Product 3 `mappend` mempty3ghci> getProduct $ Product 3 `mappend`
Product 4 `mappend` Product 224ghci> getProduct . mconcat . map Product $ [3,4,2]24
32 / 59
Sum
mappendとして*ではなく+を使うのが Sum
ghci> getSum $ Sum 2 `mappend` Sum 911ghci> getSum $ mempty `mappend` Sum 33ghci> getSum . mconcat . map Sum $ [1,2,3]8
I Num a自身はMonoidのインスタンスではない
33 / 59
AnyとAll(p.271)
Boolもモノイドにする方法が 2通りある
1. 論理和||をモノイド演算とし Falseを単位元とする
newtype Any = Any { getAny :: Bool }deriving (Eq, Ord, Read, Show, Bounded)
instance Monoid Any wheremempty = Any FalseAny x `mappend` Any y = Any (x || y)
34 / 59
Anyを使ってみる
1つでも Trueがあれば結果は Trueになる
ghci> getAny $ Any True `mappend` Any FalseTrueghci> getAny $ mempty `mappend` Any TrueTrueghci> getAny . mconcat . map Any $
[False, False, False, True]Trueghci> getAny $ mempty `mappend` memptyFalse
35 / 59
All
2. 論理積&&をモノイド演算とし Trueを単位元とする
newtype All = All { getAll :: Bool }
instance Monoid All wheremempty = All TrueAll x `mappend` All y = All (x && y)
36 / 59
Allを使ってみる
全てが Trueの場合のみ結果が Trueになる
ghci> getAll $ mempty `mappend` All TrueTrueghci> getAll $ mempty `mappend` All FalseFalseghci> getAll . mconcat . map All $ [True, True, True]Trueghci> getAll . mconcat . map All $ [True, True, False]False
I Monoidを使わなくても [Bool] -> Boolの関数 orや andがある
37 / 59
Orderingモノイド
ghci> 1 `compare` 2LTghci> 2 `compare` 2EQghci> 3 `compare` 2GT
Orderingもモノイドにできる
instance Monoid Ordering wheremempty = EQLT `mappend` _ = LTEQ `mappend` y = yGT `mappend` _ = GT
左辺の値を優先することは辞書順比較のルールに則っている
38 / 59
Orderingのモノイド則の確認
ghci> LT `mappend` GTLTghci> GT `mappend` LTGTghci> mempty `mappend` LTLTghci> mempty `mappend` GTGT
39 / 59
Orderingを使う
I 2つの文字列を引数にとりOrderingを返す関数I 長さを比較した結果を返すI 長さが同じ時は EQではなく文字列の辞書順比較の結果を返す
I 素直な書き方
lengthCompare :: String -> String -> OrderinglengthCompare x y = let a = length x `compare` length y
b = x `compare` yin if a == EQ then b else a
40 / 59
Orderingを使う 2
I モノイドを活用した書き方
import Data.MonoidlengthCompare :: String -> String -> OrderinglengthCompare x y = (length x `compare` length y)
`mappend` (x `compare` y)
ghci> lengthCompare "zen" "ants"LTghci> lengthCompare "zen" "ant"GT
41 / 59
Orderingを使う 3I 2番目に重要な条件として母音の数を比較したい
import Data.MonoidlengthCompare :: String -> String -> OrderinglengthCompare x y = (length x `compare` length y)
`mappend` (vowels x `compare` vowels y)`mappend` (x `compare` y)
where vowels = length . filter (`elem` "aeiou")
ghci> lengthCompare "zen" "anna" -- 1. 長さLTghci> lengthCompare "zen" "ana" -- 2. 母音LTghci> lengthCompare "zen" "ann" -- 3. 辞書順GT
I 比較条件に優先順位を付けるのに便利!
42 / 59
Maybeモノイド (p.275)I Maybe aも複数の方法でモノイドになれる
1. aがモノイドの時に限り Maybe aも Nothingを単位元としてJustの中身の mappendを使うモノイド
instance Monoid a => Monoid (Maybe a) wheremempty = NothingNothing `mappend` m = mm `mappend` Nothing = mJust m1 `mappend` Just m2 = Just (m1 `mappend` m2)
ghci> Nothing `mappend` Just "andy"Just "andy"ghci> Just LT `mappend` NothingJust LTghci> Just (Sum 3) `mappend` Just (Sum 4)Just (Sum {getSum = 7})
I 失敗するかもしれない計算の返り値を扱うのに便利43 / 59
First
aがモノイドでなければ使えない mappendを使わないなら任意のMaybeをモノイドにできる
2. 第一引数が Justなら第二引数を捨てる
newtype First a = First { getFirst :: Maybe a }deriving (Eq, Ord, Read, Show)
instance Monoid (First a) wheremempty = First NothingFirst (Just x) `mappend` _ = First (Just x)First Nothing `mappend` x = x
44 / 59
Firstを使ってみる
ghci> getFirst $ First (Just 'a') `mappend`First (Just 'b')
Just 'a'ghci> getFirst $ First Nothing `mappend`
First (Just 'b')Just 'b'ghci> getFirst $ First (Just 'a') `mappend`
First NothingJust 'a'ghci> getFirst . mconcat . map First $
[Nothing, Just 9, Just 10]Just 9
45 / 59
Last
3. 第 2引数が Justなら第 1引数を捨てる
I Data.Monoidに Last aも用意されている
ghci> getLast . mconcat . map Last $[Nothing, Just 9, Just 10]
Just 10ghci> getLast $ Last (Just "one") `mappend`
Last (Just "two")Just "two"
46 / 59
余談 単位元が無かったら?
モノイドは主に畳み込みに使われる
Q リストの畳み込みだけならmemptyが無くてもできるのでは?
A foldr1のように空リストの場合にエラーを吐くか,結果の型がMaybe mになる
空リストでも使える畳み込み mconcat :: [m] -> mにモノイドは必要十分
47 / 59
12.3のまとめ
I 今まで見てきた型にもモノイドがある
I newtypeで複数のインスタンスを持つ場合もI リスト
I ZipListI Num
I SumI Product
I BoolI AnyI All
I OrderingI Maybe
I FirstI Last
48 / 59
Section 4
12.4 モノイドで畳み込む (p.277)
49 / 59
Foldable
I リストが畳み込めるのは分かった.
I リスト以外のデータ構造は畳み込めないの?
それ Foldableでできるよ!
I Functor: 関数で写せるものを表す型クラスI Foldable: 畳み込みできるものを表す型クラス
Data.Foldable Foldable用の foldr, foldl, foldr1, foldl1を含む
50 / 59
Foldableを見てみよう
Preludeのものと区別するために別名を付ける
import qualified Data.Foldable as F
ghci> :t foldrfoldr :: (a -> b -> b) -> b -> [a] -> bghci> :t F.foldrF.foldr :: (F.Foldable t) =>
(a -> b -> b) -> b -> t a -> b
I t = []で等価I F.foldrは Foldableなら畳み込みできるので,より一般化
51 / 59
Foldableを使ってみよう
ghci> foldr (*) 1 [1,2,3]6ghci> F.foldr (*) 1 [1,2,3]6
リストでは同じ動作
Q リスト以外のデータ構造は?A Maybeも Foldable!
ghci> F.foldl (+) 2 (Just 9)11ghci> F.foldr (||) False (Just True)True
Nothing値が 0要素,Just値が 1要素のリストのように振る舞う
52 / 59
7章の木構造を Foldableにしてみよう
data Tree a = EmptyTree | Node a (Tree a) (Tree a)deriving (Show)
を Foldableにすれば畳み込みが可能になる.
I ある型コンストラクタを Foldableのインスタンスにするにはfoldrか foldMapを実装すれば良い
I foldMap関数の方が簡単
53 / 59
foldMapとは
foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m
I a -> m: 中身からモノイドへ変換する関数I t a: Foldableな構造I m: 結果のモノイド値I 構造の中身をモノイド値に変換してから畳み込む関数
54 / 59
Foldable Treeのインスタンス宣言
instance F.Foldable Tree wherefoldMap f EmptyTree = memptyfoldMap f (Node x l r) = F.foldMap f l `mappend`
f x `mappend`F.foldMap f r
I 左右の部分木,ノードの値それぞれがモノイドになるので
`mappend`できる.I 順番が大事!
55 / 59
Foldable Treeの使用実際に使う場合は,具体的な変換関数を渡す必要は無い
testTree = Node 5(Node 3
(Node 1 EmptyTree EmptyTree)(Node 6 EmptyTree EmptyTree)
)(Node 9
(Node 8 EmptyTree EmptyTree)(Node 10 EmptyTree EmptyTree)
)
リストと同様の畳み込み関数が使用可能に
ghci> F.foldl (+) 0 testTree42ghci> F.foldl (*) 1 testTree64800
56 / 59
foldMapを直接使うFoldableのインスタンスを定義する以外にも foldMapは役立つ
木の中に 3に等しい数があるかを調べる
ghci> getAny $ F.foldMap (\x -> Any $ x == 3) testTreeTrue
モノイド値 Anyは Trueになるものが一つでもあれば畳み込みの結果 Trueになる.
ghci> getAny $ F.foldMap (\x -> Any $ x > 15) testTreeFalse
リストもモノイドなので,任意の Foldableをリストに変換できる.
ghci> F.foldMap (\x -> [x]) testTree[1,3,6,5,8,9,10]
57 / 59
蛇足
Q なんで foldMapを定義するだけで foldr, foldl, foldr1,foldl1が定義できるんだろう?
A 本物のプログラマは Haskellを使う第 34回 様々なデータ構造で foldを使えるようにする Foldableクラス by @shelarcyhttp://itpro.nikkeibp.co.jp/article/COL-UMN/20091009/338681/
58 / 59
12.4のまとめ
I Foldableを実装するとI 任意の構造で foldファミリが使えるようになる!I 構造からリストを作るのも簡単
59 / 59