59
12 章 モノイド HATATANI Shinta(@apstndb) November 18, 2012 1 / 59

すごいH 第12章モノイド

Embed Size (px)

Citation preview

Page 1: すごいH 第12章モノイド

12章 モノイド

HATATANI Shinta(@apstndb)

November 18, 2012

1 / 59

Page 2: すごいH 第12章モノイド

自己紹介

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

Page 3: すごいH 第12章モノイド

Section 1

12.1 既存の型を新しい型にくるむ

3 / 59

Page 4: すごいH 第12章モノイド

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

Page 5: すごいH 第12章モノイド

ZipListはどう実装する?

別の型を作る必要がある

1. dataを使う

data ZipList a = ZipList [a]

値を取り出すにはパターンマッチを使う

2. dataのレコード構文を使う

data ZipList a = ZipList { getZipList :: [a] }

リストを取り出す getZipList関数が手に入る

ある型の型クラスにするには derivingと instanceを使う

5 / 59

Page 6: すごいH 第12章モノイド

newtype

3. newtypeを使う

newtypeは「1つの型を取り,それを何かにくるんで別の型に見せかける」ことに特化

実際の ZipList aの定義

newtype ZipList a = ZipList { getZipList :: [a] }

Q dataの代わりに newtypeにしただけだけど何が良くなるの?

A dataはコンストラクタに包む時も解く時もランタイムのオーバーヘッドがある

newtypeはコンパイル時のみ別の型として扱われ,ランタイムにオーバーヘッドが無い

6 / 59

Page 7: すごいH 第12章モノイド

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

Page 8: すごいH 第12章モノイド

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

Page 9: すごいH 第12章モノイド

コンストラクタの型

CharList :: [Char] -> CharList

[Char](文字列)を受け取って CharListを返す

getCharList :: CharList -> [Char]

CharListを受け取って [Char](文字列)を返す

解釈

1. 包んだりほどいたり2. 2種類の型間の変換

9 / 59

Page 10: すごいH 第12章モノイド

newtype を使って型クラスのインスタンスを作る (p.260)

型引数が一致しなくて型クラスのインスタンスにできない場合が

ある

Maybeを Functorにするのは簡単

class Functor f wherefmap :: (a -> b) -> f a -> f b

に対して f = Maybeで

instance Functor Maybe where

とし,あとは fmapを実装するだけ

10 / 59

Page 11: すごいH 第12章モノイド

タプルを 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

Page 12: すごいH 第12章モノイド

この時 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

Page 13: すごいH 第12章モノイド

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

Page 14: すごいH 第12章モノイド

例えば dataに対してパターンマッチをする関数を作る

data CoolBool = CoolBool { getCoolBool :: Bool }helloMe :: CoolBool -> StringhelloMe (CoolBool _) = "hello"

ghci> helloMe undefined"*** Exception: Prelude.undefined

dataで定義した型には複数の値コンストラクタがありうる

I (CoolBool _)にマッチするか確認できるところまで評価が必要

I undefinedを評価してしまう

14 / 59

Page 15: すごいH 第12章モノイド

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

Page 16: すごいH 第12章モノイド

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

Page 17: すごいH 第12章モノイド

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

Page 18: すごいH 第12章モノイド

12.1のまとめ

I 型シノニム (type)I 型シグネチャの整理や型名が体を表すようにしたい

I newtypeI 既存の方をある型クラスのインスタンスにしたい

I dataI 何かまったく新しいものを作りたい

18 / 59

Page 19: すごいH 第12章モノイド

Section 2

12.2 Monoid大集合 (p.265)

19 / 59

Page 20: すごいH 第12章モノイド

型クラスは同じ振る舞いをする型のインターフェース

I Eq 等号が使えるI Ord 順序が付けられるI Functor, Applicative …

新しい型を作る時は欲しい機能の型クラスを実装する

20 / 59

Page 21: すごいH 第12章モノイド

こんな型はどうだろう?

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

Page 22: すごいH 第12章モノイド

共通の性質

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

Page 23: すごいH 第12章モノイド

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

Page 24: すごいH 第12章モノイド

モノイド則 (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

Page 25: すごいH 第12章モノイド

12.2のまとめ

モノイドは

I 同じ型の 2つの値からその型の 1つの値を作る 2項演算I モノイド則を守る必要がある

I 単位元I 結合的

25 / 59

Page 26: すごいH 第12章モノイド

Section 3

12.3 モノイドとの遭遇 (p.268)

26 / 59

Page 27: すごいH 第12章モノイド

リストはモノイド (p.268)

instance Monoid [a] wheremempty = []mappend = (++)

リストは中身の型に関わらずMonoid

27 / 59

Page 28: すごいH 第12章モノイド

モノイドとしてリストを使う

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

Page 29: すごいH 第12章モノイド

モノイド則に交換法則はない

モノイド則は a `mappend` b = b `mappend` aを要求しない

交換法則は*は満たすが殆どのモノイドは満たさない!

ghci> "one" `mappend` "two""onetwo"ghci> "two" `mappend` "one""twoone"

29 / 59

Page 30: すごいH 第12章モノイド

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

Page 31: すごいH 第12章モノイド

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

Page 32: すごいH 第12章モノイド

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

Page 33: すごいH 第12章モノイド

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

Page 34: すごいH 第12章モノイド

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

Page 35: すごいH 第12章モノイド

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

Page 36: すごいH 第12章モノイド

All

2. 論理積&&をモノイド演算とし Trueを単位元とする

newtype All = All { getAll :: Bool }

instance Monoid All wheremempty = All TrueAll x `mappend` All y = All (x && y)

36 / 59

Page 37: すごいH 第12章モノイド

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

Page 38: すごいH 第12章モノイド

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

Page 39: すごいH 第12章モノイド

Orderingのモノイド則の確認

ghci> LT `mappend` GTLTghci> GT `mappend` LTGTghci> mempty `mappend` LTLTghci> mempty `mappend` GTGT

39 / 59

Page 40: すごいH 第12章モノイド

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

Page 41: すごいH 第12章モノイド

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

Page 42: すごいH 第12章モノイド

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

Page 43: すごいH 第12章モノイド

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

Page 44: すごいH 第12章モノイド

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

Page 45: すごいH 第12章モノイド

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

Page 46: すごいH 第12章モノイド

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

Page 47: すごいH 第12章モノイド

余談 単位元が無かったら?

モノイドは主に畳み込みに使われる

Q リストの畳み込みだけならmemptyが無くてもできるのでは?

A foldr1のように空リストの場合にエラーを吐くか,結果の型がMaybe mになる

空リストでも使える畳み込み mconcat :: [m] -> mにモノイドは必要十分

47 / 59

Page 48: すごいH 第12章モノイド

12.3のまとめ

I 今まで見てきた型にもモノイドがある

I newtypeで複数のインスタンスを持つ場合もI リスト

I ZipListI Num

I SumI Product

I BoolI AnyI All

I OrderingI Maybe

I FirstI Last

48 / 59

Page 49: すごいH 第12章モノイド

Section 4

12.4 モノイドで畳み込む (p.277)

49 / 59

Page 50: すごいH 第12章モノイド

Foldable

I リストが畳み込めるのは分かった.

I リスト以外のデータ構造は畳み込めないの?

それ Foldableでできるよ!

I Functor: 関数で写せるものを表す型クラスI Foldable: 畳み込みできるものを表す型クラス

Data.Foldable Foldable用の foldr, foldl, foldr1, foldl1を含む

50 / 59

Page 51: すごいH 第12章モノイド

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

Page 52: すごいH 第12章モノイド

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

Page 53: すごいH 第12章モノイド

7章の木構造を Foldableにしてみよう

data Tree a = EmptyTree | Node a (Tree a) (Tree a)deriving (Show)

を Foldableにすれば畳み込みが可能になる.

I ある型コンストラクタを Foldableのインスタンスにするにはfoldrか foldMapを実装すれば良い

I foldMap関数の方が簡単

53 / 59

Page 54: すごいH 第12章モノイド

foldMapとは

foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m

I a -> m: 中身からモノイドへ変換する関数I t a: Foldableな構造I m: 結果のモノイド値I 構造の中身をモノイド値に変換してから畳み込む関数

54 / 59

Page 55: すごいH 第12章モノイド

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

Page 56: すごいH 第12章モノイド

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

Page 57: すごいH 第12章モノイド

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

Page 58: すごいH 第12章モノイド

蛇足

Q なんで foldMapを定義するだけで foldr, foldl, foldr1,foldl1が定義できるんだろう?

A 本物のプログラマは Haskellを使う第 34回 様々なデータ構造で foldを使えるようにする Foldableクラス by @shelarcyhttp://itpro.nikkeibp.co.jp/article/COL-UMN/20091009/338681/

58 / 59

Page 59: すごいH 第12章モノイド

12.4のまとめ

I Foldableを実装するとI 任意の構造で foldファミリが使えるようになる!I 構造からリストを作るのも簡単

59 / 59