12 章 モノイドHATATANI Shinta(@apstndb)    November 18, 2012                            1 / 59
自己紹介   @apstndb   千葉工業大学@津田沼 修士課程   好きな言語は C++       BoostCon 2011 によると「Haskell は C++ TMP のための擬       似言語]       http://bo...
Section 112.1 既存の型を新しい型にくるむ                     3 / 59
11 章のおさらい リストをアプリカティブファンクタにする方法は複数 1. 左辺のリストの関数と右辺のリストの値の全組み合わせ ghci> [(+1),(*100),(*5)] <*> [1,2,3] [2,3,4,100,200,300,5,...
ZipList はどう実装する? 別の型を作る必要がある 1. data を使う data ZipList a = ZipList [a] 値を取り出すにはパターンマッチを使う 2. data のレコード構文を使う data ZipList a...
newtype  3. newtype を使う  newtype は「1 つの型を取り,それを何かにくるんで別の型に見  せかける」ことに特化  実際の ZipList a の定義  newtype ZipList a = ZipList { ...
newtype 2            Q 常に data の代わりに newtype を使うのは?            A newtype は値コンストラクタもフィールドも 1 つ              data は値コンストラクタも...
newtype で deriving     可能なのは Eq, Ord, Enum, Bounded, Show, Read         GHC 拡張 GeneralizedNewtypeDeriving で任意の型クラス     包む型...
コンストラクタの型 CharList :: [Char] -> CharList [Char](文字列) を受け取って CharList を返す getCharList :: CharList -> [Char] CharList を受け取って...
newtype を使って型クラスのインスタンスを作る (p.260) 型引数が一致しなくて型クラスのインスタンスにできない場合が ある Maybe を Functor にするのは簡単 class Functor f where     fmap...
タプルを Functor に          Q タプルを Functor にするには?            例えば,fmap が第一要素に対して働くようにしたい            (fmap (+3) (1, 1) が (4, 1) ...
この時 fmap の型はfmap :: (a -> b) -> Pair c a -> Pair c bclass Functor f where    fmap :: (a -> b) -> f a -> f bの f = Pair c とな...
newtype と遅延評価 (p.261)  newtype は既存の型を新しい型に変えるだけ  これらを Haskell は型としては区別するが,同一の内部表現を使う  newtype は data より高速なだけでなくパターンマッチがより怠...
例えば data に対してパターンマッチをする関数を作るdata CoolBool = CoolBool { getCoolBool :: Bool }helloMe :: CoolBool -> StringhelloMe (CoolBool...
data ではなく newtype を使ったら?  newtype CoolBool = CoolBool { getCoolBool :: Bool }  helloMe :: CoolBool -> String  helloMe (Coo...
type vs. newtype vs. data(p.263)   type 既存の型に型シノニム (別名) を与える   type IntList = [Int]   [Int] 型に IntList という別名を付けただけで,値コンストラ...
newtype 既存の型から新しい型を作る型クラスのインスタンスを作るために使用newtype CharList = CharList { getCharList :: [Char] }   CharList と [Char] を++で連結する...
12.1 のまとめ    型シノニム (type)       型シグネチャの整理や型名が体を表すようにしたい    newtype       既存の方をある型クラスのインスタンスにしたい    data       何かまったく新しいものを...
Section 212.2 Monoid 大集合 (p.265)                          19 / 59
型クラスは同じ振る舞いをする型のインターフェース   Eq 等号が使える   Ord 順序が付けられる   Functor, Applicative … 新しい型を作る時は欲しい機能の型クラスを実装する                     ...
こんな型はどうだろう?    *は 2 つの数を取って掛け算する関数        1 * xもx * 1もx ghci> 4 * 1 4 ghci> 1 * 9 9    ++は二つのリストを連結する関数        x ++ [] も [...
共通の性質    関数の引数は 2 つ        2 つの引数と返り値の型は同じ    相手を変えない特殊な値 (単位元) が存在する    3 つ以上の値をまとめる時,計算する順序を変えても同じ結    果 (結合的: associati...
Monoid 型クラス (p.266)  Data.Monoid に定義されている Monoid の定義  class Monoid m where      mempty :: m      mappend :: m -> m -> m   ...
モノイド則 (p.267)  モノイドが満たすべき法則    単位元       mempty `mappend` x = x       x `mappend` mempty = x    結合的       (x `mappend` y) ...
12.2 のまとめ  モノイドは    同じ型の 2 つの値からその型の 1 つの値を作る 2 項演算    モノイド則を守る必要がある      単位元      結合的                                    ...
Section 312.3 モノイドとの遭遇 (p.268)                        26 / 59
リストはモノイド (p.268)  instance Monoid [a] where      mempty = []      mappend = (++)  リストは中身の型に関わらず Monoid                    ...
モノイドとしてリストを使う ghci> [1,2,3] `mappend` [4,5,6] [1,2,3,4,5,6] ghci> ("one" `mappend` "two") `mappend` "tree" "onetwotree" gh...
モノイド則に交換法則はない モノイド則は a `mappend` b = b `mappend` a を要求しない 交換法則は*は満たすが殆どのモノイドは満たさない! ghci> "one" `mappend` "two" "onetwo" g...
Product と Sum(p.269)  数をモノイドにする方法は 2 つある      *を演算にして 1 を単位元にする      +を演算にして 0 を単位元にする  ghci>   0 + 4  4  ghci>   5 + 0  5...
Product の定義とインスタンス宣言 in Data.Monoid  newtype Product a = Product { getProduct :: a }      deriving (Eq, Ord, Read, Show, B...
Product を使ってみる  全ての Num のインスタンス a に対して Product が使える  ghci> getProduct $ Product   3 `mappend` Product 9  27  ghci> getProd...
Sum  mappend として*ではなく+を使うのが Sum  ghci> getSum $ Sum 2 `mappend` Sum 9  11  ghci> getSum $ mempty `mappend` Sum 3  3  ghci>...
Any と All(p.271)   Bool もモノイドにする方法が 2 通りある   1. 論理和||をモノイド演算とし False を単位元とする   newtype Any = Any { getAny :: Bool }       ...
Any を使ってみる 1 つでも True があれば結果は True になる ghci> getAny $ Any True `mappend` Any False True ghci> getAny $ mempty `mappend` An...
All      2. 論理積&&をモノイド演算とし True を単位元とする      newtype All = All { getAll :: Bool }      instance Monoid All where          ...
All を使ってみる 全てが True の場合のみ結果が True になる ghci>   getAll $ mempty `mappend` All True True ghci>   getAll $ mempty `mappend` Al...
Ordering モノイド  ghci> 1 `compare` 2  LT  ghci> 2 `compare` 2  EQ  ghci> 3 `compare` 2  GT  Ordering もモノイドにできる  instance Mon...
Ordering のモノイド則の確認  ghci>   LT `mappend` GT  LT  ghci>   GT `mappend` LT  GT  ghci>   mempty `mappend` LT  LT  ghci>   mem...
Ordering を使う     2 つの文字列を引数にとり Ordering を返す関数         長さを比較した結果を返す         長さが同じ時は EQ ではなく文字列の辞書順比較の結果を         返す     素直な...
Ordering を使う 2     モノイドを活用した書き方  import Data.Monoid  lengthCompare :: String -> String -> Ordering  lengthCompare x y = (l...
Ordering を使う 3     2 番目に重要な条件として母音の数を比較したい  import Data.Monoid  lengthCompare :: String -> String -> Ordering  lengthCompa...
Maybe モノイド (p.275)     Maybe a も複数の方法でモノイドになれる  1. a がモノイドの時に限り Maybe a も Nothing を単位元として     Just の中身の mappend を使うモノイド  i...
First   a がモノイドでなければ使えない mappend を使わないなら任意の   Maybe をモノイドにできる   2. 第一引数が Just なら第二引数を捨てる   newtype First a = First { getFi...
First を使ってみる  ghci> getFirst $ First (Just a) `mappend`                   First (Just b)  Just a  ghci> getFirst $ First N...
Last   3. 第 2 引数が Just なら第 1 引数を捨てる       Data.Monoid に Last a も用意されている   ghci> getLast . mconcat . map Last $            ...
余談 単位元が無かったら? モノイドは主に畳み込みに使われる       Q リストの畳み込みだけなら mempty が無くてもでき         るのでは?       A foldr1 のように空リストの場合にエラーを吐くか,      ...
12.3 のまとめ    今まで見てきた型にもモノイドがある    newtype で複数のインスタンスを持つ場合も      リスト             ZipList      Num             Sum          ...
Section 412.4 モノイドで畳み込む (p.277)                         49 / 59
Foldable       リストが畳み込めるのは分かった.            リスト以外のデータ構造は畳み込めないの?   それ Foldable でできるよ!       Functor: 関数で写せるものを表す型クラス       ...
Foldable を見てみよう  Prelude のものと区別するために別名を付ける  import qualified Data.Foldable as F  ghci> :t foldr  foldr :: (a -> b -> b) ->...
Foldable を使ってみよう  ghci> foldr (*) 1 [1,2,3]  6  ghci> F.foldr (*) 1 [1,2,3]  6  リストでは同じ動作          Q リスト以外のデータ構造は?        ...
7 章の木構造を Foldable にしてみよう  data Tree a = EmptyTree | Node a (Tree a) (Tree a)      deriving (Show)  を Foldable にすれば畳み込みが可能に...
foldMap とは  foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m     a -> m: 中身からモノイドへ変換する関数     t a: Foldable な構造   ...
Foldable Tree のインスタンス宣言  instance F.Foldable Tree where      foldMap f EmptyTree = mempty      foldMap f (Node x l r) = F....
Foldable Tree の使用  実際に使う場合は,具体的な変換関数を渡す必要は無い  testTree = Node 5              (Node 3                  (Node   1 EmptyTree ...
foldMap を直接使う  Foldable のインスタンスを定義する以外にも foldMap は役立つ  木の中に 3 に等しい数があるかを調べる  ghci> getAny $ F.foldMap (x -> Any $ x == 3) ...
蛇足     Q なんで foldMap を定義するだけで foldr, foldl, foldr1,       foldl1 が定義できるんだろう?     A 本物のプログラマは Haskell を使う       第 34 回 様々なデ...
12.4 のまとめ    Foldable を実装すると       任意の構造で fold ファミリが使えるようになる!       構造からリストを作るのも簡単                                    59 /...
Upcoming SlideShare
Loading in …5
×

すごいH 第12章モノイド

2,352 views

Published on

0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,352
On SlideShare
0
From Embeds
0
Number of Embeds
223
Actions
Shares
0
Downloads
18
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

すごいH 第12章モノイド

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

×