すごいHASKELL第12章: モノイド
内容newtype
既存の型から新しい型を作るためのキーワード
Monoidの説明で多用
Monoid
値を二項演算子で結合できるような型
さまざまなクラスに対する実例
Data.Foldable
モノイドの性質を持つ値に対する畳込み
復習型コンストラクタ
data CMaybe a = ...でいう左辺、aは型をとる
抽象型から具体型を作る
値コンストラクタ
data構文の右辺に来る部分、|で区切ることで複数持つこ
とも可能
値コンストラクタは単なる関数という理解
レコード構文
data Employee = Employee { name :: String, age :: Int,
address :: String, unit :: String} deriving (Show, Eq)
上記の場合、name Employeeなどで各フィールドにアク
セスするための関数が勝手に追加される
NEWTYPEキーワードnewtype ZipList a = ZipList { getZipList :: [a] }のように使
う新しいキーワード
dataキーワード
独自の代数データ型を作るためのキーワード
typeキーワード
既存の型に型シノニムを与える
newtypeキーワード
既存の型から新たな型を作る
値コンストラクタが1つだけ、フィールドも1つだけとい
う制限の付いたdata宣言だとみなせる
リストにおけるNEWTYPEの例説明の流れ
1. リストをアプリカティブファンクタにする方法(2種類)
2. dataキーワードによるデータ宣言
1. ZipListの例
2. 13章のCMaybe a型の例
3. newtypeキーワードによるデータ宣言
リストをアプリカティブファンクタにする方法1. <*>が左辺のリストから関数を1つずつ取り出して右辺の
リストの中の値全てに適用(直積集合)
2. <*>の左辺と右辺で対応する位置同士で左辺の関数を右辺
の値に適用(ZipList利用)
import Control.Applicative
-- 1番目の例
print ([(+1), (*100), (*5)] <*> [1,2,3])
-- 2番目の例
print (ZipList [(+1), (*100), (*5)] <*> ZipList [1,2,3])
print (getZipList $ ZipList [(+1), (*100), (*5)] <*> ZipList [1,2,3])
[2,3,4,100,200,300,5,10,15]
ZipList {getZipList = [2,200,15]}
[2,200,15]
ZIPLIST A型のデータ宣言の書き方1つの方法: data ZipList a = ZipList [a]
ZipList a型には値コンストラクタが1つ( ZipList [a] )しか
なく、その値コンストラクタにはフィールドが1つ( [a])
ZipListからリストを取り出す関数が自動的に手に入るよう
にレコード構文を使っても良い
data ZipList a = ZipList { getZipList :: [a] }
これらの方法は別に悪くはないし実際うまく動く
11章でのCMAYBE A型におけるDATAキーワードZipListでは既存の型を別の型でくるむために使用
11章ではCMaybe型を定義する際にdataキーワードを使用
data CMaybe a = CNothing | CJust Int a deriving (Show)
CMaybeクラスをShow型クラスのインスタンスにするた
めにderivingを使用
Applicative型クラスのインスタンスにするために
instanceを使用
このように既存の型をある型クラスのインスタンスにす
る方法は2つ(derivingとinstance)
NEWTYPEキーワードZipList a, CMaybe aのように「1つの型を取り、それを何か
にくるんで別の型に見せかけたい」場合に用いる
ZipList aの実際の定義
newtype ZipList a = ZipList { getZipList :: [a] }
NEWTYPEの特徴(1)
dataより高速に動作
既存の型をくるむことがhaskellに伝わるため内部処理を
同じままに保て、オーバーヘッドを減らせる
値コンストラクタは1種類しか持てず、その値コンストラ
クタが持つフィールドも1つだけという制限
dataキーワードを使うと複数の値コンストラクタが持
て、各コンストラクタは0以上の任意個数のフィールド
が持てる
data Profession = Fighter | Archer | Accountant
data Race = Human | Elf | orc | Goblin
data PlayerCharacter = PlayerCharacter Race Profession
NEWTYPEの特徴(2)
derivingも使える
Eq, Ord, Enum, Bounded, Show, Readのインスタンスを導
出可能
GHC言語拡張のGeneralizedNewtypeDerivingを使うと元
の型が所属していた任意の型クラスを導出可能
ある型クラスのインスタンスを導出したい場合は包む元
の型がその型クラスに属している必要有
他の型を包むだけなので当然
-- [Char]でなくStringにしないと怒られる
newtype CharList = CharList { getCharList :: [Char] } deriving (Eq, Show)
print (CharList "this will be shown!")
print (CharList "benny" == CharList "benny")
print (CharList "benny" == CharList "oisters")
:t CharList -- 型コンストラクタの型
:t getCharList -- CharListと[Char]という2種類の型の間の変換
Use String
Found: [Char] Why Not: String
CharList {getCharList = "this will be shown!"}
True
False
CharList :: [Char] -> CharList
getCharList :: CharList -> [Char]
NEWTYPEを使って型クラスのインスタンスを作るある型を型クラスのインスタンスにしたいが型引数が一致
しなくてできないことはよくある
MAYBEをFUNCTORのインスタンスにする例Functor型クラスの定義
class Functor f where fmap :: (a -> b) -> f a -> f b
Maybeの場合はfがMaybeとすれば良い
instance Functor Maybe where fmap :: (a -> b) -> Maybe a ->
Maybe b
タプルをFUNCTORのインスタンスにする例fmapをタプルに作用させて第一の要素(fst)を変更するよう
にしたい
fmap (+3) (1, 1)と書くと(4, 1)になるような処理
型(a, b)は型変数が1つでないため書きにくい
fmapが変更するのが型aの部分と指定できない
解決策タプルをnewtypeして2つの型引数の順番を入れ替える
newtype Pair b a = Pair { getPair :: (a, b) }
fmapが第一要素に作用するような形でタプルをFunctor
のインスタンスにできる
newtype Pair b a = Pair { getPair :: (a, b) }
-- パターンマッチを使ってPair型から中身のタプルを取り出し、
-- fをタプルの第一要素に適用し、
-- Pair値コンストラクタを使ってタプルをPair b a型に再変換
instance Functor (Pair c) where
fmap f (Pair (x, y)) = Pair (f x, y)
-- fmapの型(a -> b) -> f a -> f bは
-- Pair b a型の場合は
-- (a -> b) -> Pair c a -> Pair c b
:type fmap
:type fmap (*100) Pair
print (getPair $ fmap (*100) (Pair (2, 3)))
print (getPair $ fmap reverse (Pair ("london calling", 3)))
fmap :: forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (*100) Pair :: forall b a. Num (Pair b a) => (a, b) -> Pair b a
(200,3)
("gnillac nodnol",3)
NEWTYPEと遅延評価newtypeでできることは既存の型を新しい型に変えること
だけ
元の型と型としては区別しつつ同一の内部表現で扱う
dataよりパターンマッチがより怠惰になる
-- undefinedはブッ壊れた計算を表すHaskellの値
-- 評価すると怒りを爆発させる(専門用語では例外を投げる)
undefined
Prelude.undefined
-- Haskellはデフォルトで遅延評価であるので
-- undefinedを要素に含むリストに対するheadは先頭さえundefinedでなければ成功
print (head [3,4,5,undefined,2,undefined])
3
-- Bool型のフィールドを1つ持つ値コンストラクタで生成可能なCoolBool型を作る
data CoolBool = CoolBool { getCoolBool :: Bool }
-- 中身のBoolがTrueであるかFalseであるかによらず"hello"を返す関数を書く
helloMe :: CoolBool -> String
helloMe (CoolBool _) = "hello"
-- undefinedに対してhelloMeを適用
-- -> 例外を吐く
print (helloMe undefined)
Prelude.undefined
例外が出る理由dataキーワードで定義された型は複数の値コンストラクタ
があるかもしれない
(CoolBool _)に合致するかどうかを確認するためにはどの
コンストラクタが使われたのか分かるところまで引数の評
価を進める必要がある
undefinedを少しでも評価しようとすると例外が発生
-- dataではなくnewtypeを使ってCoolBoolを定義
newtype CoolBool = CoolBool { getCoolBool :: Bool }
-- helloMe関数を変更する必要はない(パターンマッチの構文は共通)
-- と書いてあるが再定義しないと例外が発生したため再定義
helloMe :: CoolBool -> String
helloMe (CoolBool _) = "hello"
-- 実行
print (helloMe undefined)
"hello"
動作する理由newtypeキーワードでは1つしか値コンストラクタを使えな
い
haskellはそのことを知っているためhelloMe undefined
が(CoolBool _)のパターンにマッチすると判定
些細な違いに思えるが重要な違い
dataキーワードで定義した型とnewtypeキーワードで定
義した型は異なったメカニズムで動作
dataはオリジナルな型を無から作り出す
newtypeは既存の型をもとにはっきり区別される新し
い型を作る
パターンマッチ
dataに対しては箱から中身を取り出す
newtypeに対してはある型を別の型へ直接変換
TYPE VS. NEWTYPE VS. DATA
3つがどう違うのか整理
TYPE
型シノニムを作るためのもの
type IntList = [Int]とすると[Int]型をIntListとも呼べるよ
うになる
2つの呼び名は自由に交換可能
IntList型の値コンストラクタは一切生じない
型注釈にどちらの名前を使うかは自由
type IntList = [Int]
print (([1,2,3] :: IntList) ++ ([1,2,3] :: [Int]))
[1,2,3,1,2,3]
型シノニムを使用する場面型シグネチャを整理して分かりやすくしたいときに使う
特定のものを表すために複雑な型を作る際など
7章の例
電話帳を[(String, String)]という連想リストで表
しPhoneBookという型シノニムで表現
NEWTYPE
既存の型を包んで新しい型を作るためのもの
型クラスのインスタンスを作りやすくするために使われる
作られた型は元の型とは別物
newtype CharList = CharList { getCharList :: [Char] }
-- CharListはリストを中身に持ってはいるがリストそのものではないため
-- これはできない
print (CharList "CharList string ++ " ++ "[Char] string")
Use String
Found: [Char] Why Not: String
Couldn't match expected type ‘String’ with actual type ‘CharList’
In the first argument of ‘(++)’, namely ‘CharList "CharList string ++ "’
In the first argument of ‘print’, namely ‘(CharList "CharList string ++ " ++ "String string")’
-- 一旦リストにすると結合できる
print (getCharList (CharList "CharList string ++ ") ++ "[Char] string")
"CharList string ++ [Char] string"
NEWTYPE宣言におけるレコー
ド構文新しい型と元の型を相互変換する関数が自動的に作られる
newtypeの値コンストラクタ(元の型->新しい型)
フィールド内の値を取り出す関数(新しい型->元の型)
NEWTYPE宣言の特徴新しい型は元の型の所属していた型クラスを引き継がない
derivingで導出するかインスタンス宣言を手書きする必
要がある
(脚注)あるいはGeneralizedNewtypeDeriving拡張を使
う
newtype宣言は値コンストラクタが1つだけ、フィールドも
1つだけ、という制限のついたdata宣言だとみなしても実用
上は問題ない
そのようなdata宣言を見つけたらnewtypeで代用できな
いか要検討
DATA
自作の新しいデータ型を作るためのもの
フィールドと値コンストラクタを山ほど備えた途方も無い
データ型だろうと作り出せる
リスト、Maybe, Treeなどなんでも
まとめ型シグネチャを整理したい、型名が体を表すようにしたい
だけなら型シノニム
既存の型をある型クラスのインスタンスにしたくて新しい
型にくるむ方法を探しているのならnewtype
何か全く新しいものを作りたい場合はdata
MONOID大集合Haskellの型クラス
同じ振る舞いをする型たちに共通のインターフェイスを
提供
Eq, Ord, Functor, Applicative
型の設計
型に欲しい機能をもとにどの型クラスのインスタンスを
実装するかを検討
型の値の等号の定義に意味があるならEq, ファンクタ
にしたいならFunctorのインスタンスにする
乗算、リストの結合2引数関数(*)、2引数関数(++)には引数に適用してももう一
方の値を変えないような値が存在
1 * xとx * 1の結果は等しい
[] ++ xsとxs ++ []は等しい
複数の値を1つにまとめる計算をする時、値の間に関数を
挟む順序を変えても結果は変わらない
(3 * 4) * 5と3 * (4 * 5)は等しい
"la" ++ ("di" ++ "ga")と("la" ++ "di") ++ "ga"は等し
い
print (4 * 1)
print (1 * 4)
print ([1,2,3] ++ [])
print ([] ++ [0.5, 2.5])
-- 関数を挟む順序を変えても結果は変わらない
print ((3 * 2) * (8 * 5))
print (3 * (2 * (8 * 5)))
print ("la" ++ ("di" ++ "ga"))
print (("la" ++ "di") ++ "ga")
Evaluate
Found: 4 * 1 Why Not: 4
4
4
[1,2,3]
[0.5,2.5]
240
240
"ladiga"
"ladiga"
*と++に共通の性質性質
関数は引数を2つ取る
2つの引数および返り値の型はすべて等しい
2引数関数を施して相手を変えないような特殊な値が存
在
2項演算が結合的(associativity)
以上の性質がモノイドの性質
結合的でない演算の例-の演算子
(5 - 3) - 4と5 - (3 - 4)は異なる結果
MONOID型クラスモノイド
結合的な二項演算子(2引数関数)とその演算に関する単位
元からなる構造
単位元
単位元と他の値を2引数関数に適用した時に返り値が
他の値に等しくなる要素
1は*の単位元であり[]は++の単位元
HaskellにはMonoid型クラスが用意
-- Monoid型クラスの定義
-- (Data.Monoidモジュールにて定義されている)
--
-- mは型クラスの関数定義中で型引数を取らないため
-- FunctorやApplicativeとは違って具体型となる
--
-- class Functor f where
-- fmap :: (a -> b) -> f a -> f b
--
class Monoid m where
mempty :: m
mappend :: m -> m -> m
mconcat :: [m] -> m
mconcat = foldr mappend mempty
3つの関数(1)
mempty :: m
単位元を表す多相定数
BoundedのminBoundみたいなもの
mappend :: m -> m -> m
同じ型の引数を2つ取りその型の別の値を返す二項演算
(++)に対してはappendで良いが*に対してはappendの要
素はないため名前が残念
3つの関数(2)
mconcat
モノイドのリストを取ってmappendを間に挟んだ式を作
り単一の値を計算する関数
デフォルト実装有
memptyを初期値に取りリストをmappendで右畳み込
み
ほとんどの場合これで十分なので自分で型クラスのイ
ンスタンスを作るときはmemptyとmappendだけ実装
すれば良い
モノイド則Monoidが満たすべき法則が存在
満たさなくてもMonoidインスタンスになれるが誰も得し
ない
満たすかどうかはプログラマが確認する必要有
モノイド則
mempty mappend x = x
x mappend mempty = x
(x mappend y) mappend z = x mappend (y mappend z)
モノイドとの遭遇Monoidインスタンスの例
リスト
Product, Sum
Any, All
Ordering
Maybe a
リストの例import Data.Monoid
-- リストのモノイド型クラスに対するインスタンス宣言
-- Monoid []でなくMonoid [a]となっているのはMonoidのインスタンスは具体型であるため
-- 実際にはData.Monoidで定義
--
{-
instance Monoid [a] where
mempty = []
mappend = (++)
-}
-- 使ってみる
print ([1,2,3] `mappend` [4,5,6])
print (("one" `mappend` "two") `mappend` "tree")
print ("one" `mappend` ("two" `mappend` "tree"))
print ("one" `mappend` "two" `mappend` "tree")
print ("pang" `mappend` mempty)
print (mconcat [[1,2], [3,6], [9]]) -- デフォルト実装が利用される
-- mempty単体だとどの型に対するmemptyであるか分からないため
-- リストのインスタンスであることを明示
-- 空リストは任意の型に対して適用可能であるため[a]で良い
print (mempty :: [a])
-- モノイド則はa `mappend` bとb `mappend` aが等しいことは要求していない
print ("one" `mappend` "two")
print ("two" `mappend` "one")
[1,2,3,4,5,6]
"onetwotree"
"onetwotree"
"onetwotree"
"pang"
[1,2,3,6,9]
[]
"onetwo"
"twoone"
PRODUCTとSUM
数をモノイドにする2つの方法
*を2項演算にして1を単位元にする
+を2項演算子して0を単位元にする
print (0 + 4)
print (5 + 0)
print ((1 + 3) + 5)
print (1 + (3 + 5))
4
5
9
9
ある型に対して同じ型クラスのインスタンスを複数定義数に対して複数のインスタンスを定義したい
newtypeにくるんで新しい型をインスタンスにする
Data.Monoidモジュール
ProductとSumという2つの型をエクスポートしている
import Data.Monoid
-- SumとProductのnewtype宣言とインスタンス宣言
-- 実際にはData.Monoidで定義
{-
-- netypeの定義
newtype Product a = Product { getProduct :: a }
deriving (Eq, Ord, Read, Show, Bounded)
newtype Sum a = Sum { getSum :: 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)
instance Num a => Monoid (Sum a) where
mempty = Sum 0
Sum x `mappend` Sum y = Sum (x + y)
-}
-- 使ってみる
print "Product example"
print (getProduct $ Product 3 `mappend` Product 9)
print (getProduct $ Product 3 `mappend` mempty)
print (getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2)
print (getProduct . mconcat . map Product $ [3,4,2])
print "Sum example"
print (getSum $ Sum 2 `mappend` Sum 9)
print (getSum $ mempty `mappend` Sum 3)
print (getSum . mconcat . map Sum $ [1,2,3])
"Product example"
27
3
24
24
"Sum example"
11
3
6
ANYとALL
Boolに対するモノイド
||をモノイド演算としFalseを単位元とする
newtypeでAnyが定義
&&をモノイド演算としTrueを単位元とする
newtypeでAllが定義
import Data.Monoid
-- AnyとAllのnewtype宣言とインスタンス宣言
-- 実際にはData.Monoidで定義
{-
-- netypeの定義
newtype Any = Any { getAny :: Bool }
deriving (Eq, Ord, Read, Show, Bounded)
newtype All = All { getAll :: Bool }
deriving (Eq, Ord, Read, Show, Bounded)
-- インスタンス宣言の例
instance Monoid Any where
mempty = Any False
Any x `mappend` Any y = Any (x || y)
instance Monoid All where
mempty = All True
All x `mappend` All y = All (x && y)
-}
-- 使ってみる
print "Any example"
print (getAny $ Any True `mappend` Any False)
print (getAny $ mempty `mappend` Any True)
print (getAny . mconcat . map Any $ [False, False, False, True])
print (getAny $ mempty `mappend` mempty)
print "All example"
print (getAll $ mempty `mappend` All True)
print (getAll $ mempty `mappend` All False)
print (getAll . mconcat . map All $ [True, True, True])
print (getAll . mconcat . map All $ [True, True, False])
{-
こんなふうに包んだり解いたりするのは面倒なので普通はandやorの関数と[Bool]を使う
-}
"Any example"
True
True
True
False
"All example"
True
False
True
False
ORDERINGモノイドOrdering型
ものを比較した結果を表すのに使う型
LT, EQ, GTという3つの値のいずれかを取る
:type compare
print (1 `compare` 2)
print (2 `compare` 2)
print (3 `compare` 2)
compare :: forall a. Ord a => a -> a -> Ordering
LT
EQ
GT
import Data.Monoid
-- OrderingのMonoidインスタンス
-- 2つのOrdering値をmappendすると左辺の値が優先されるが
-- 左辺がEQである場合は別
-- 単位元はEQ
-- 実際にはData.Monoidで定義
{-
instance Monoid Ordering where
mempty = EQ
LT `mappend` _ = LT
EQ `mappend` y = y
GT `mappend` _ = GT
-}
-- 実例
print (LT `mappend` GT)
print (GT `mappend` LT)
print (mempty `mappend` LT)
print (mempty `mappend` GT)
LT
GT
LT
GT
ORDERINGのMONOID
文字列を辞書順で比較する時のルールに合わせた定義
"OX"と"ON"の比較1. oとoをまず比較
2. 一致した(EQ)のでxとnを比較
3. GTが返る
どんな時に便利か2つの文字列の長さを比較してOrderingを返す関数
長さが等しい時は辞書順比較する
-- 長さの比較結果をa, 辞書順の比較結果をbとし、長さが等しければbを返す
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
-- Orderingはモノイドであるという知識を使えばもっとシンプルに書ける
import Data.Monoid
lengthCompare :: String -> String -> Ordering
lengthCompare x y = (length x `compare` length y) `mappend` (x `compare` y)
-- 試してみる
print (lengthCompare "zen" "ants")
print (lengthCompare "zen" "ant")
LT
GT
-- 単語の中の母音の数も比較して2番目に重要な条件にしたパターン
import Data.Monoid
-- 注: `elem`が勝手に∈に変換される
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")
print (lengthCompare "zen" "anna")
print (lengthCompare "zen" "ana")
print (lengthCompare "zen" "ann")
LT
LT
GT
MAYBEモノイドMaybe aをモノイドにする方法
型引数aがモノイドである時に限りMaybe aもモノイドで
あるとし、Justの中身のmappendを使って定義
Nothingであれば別の片方の値を使う
import Data.Monoid
-- Maybe a型に対するインスタンス定義、aがMonoidである制約を付けている
-- 実際にはData.Monoidで宣言されている
{-
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)
-}
-- 使ってみる
print (Nothing `mappend` Just "andy")
print (Just LT `mappend` Nothing)
print (Just (Sum 3) `mappend` Just (Sum 4))
Just "andy"
Just LT
Just (Sum {getSum = 7})
MAYBEの中身がMONOIDのインスタンスでない場合中身がモノイドであることを利用したのはmappendの両辺
がJustである場合
中身がモノイドであるかどうか分からない状態では
mappendは使えない
中身がモノイドであるか分からない場合
中身の演算は諦める
2つのMaybe a型の値のどちらかのみを採用することにす
る
Maybe a型をnewtypeで包み、その型のモノイド型ク
ラスのインスタンスの定義を"第一引数を優先して返
す"定義とする
import Data.Monoid
-- Firstのnewtypeとインスタンス宣言
-- 実際にはData.Monoidで定義
{-
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
-}
-- 試す
print (getFirst $ First (Just 'a') `mappend` First (Just 'b'))
print (getFirst $ First Nothing `mappend` First (Just 'b'))
print (getFirst $ First (Just 'a') `mappend` First Nothing)
-- リストの中にJust値があるかも調べられる
print (getFirst . mconcat . map First $ [Nothing, Just 9, Just 10])
Just 'a'
Just 'b'
Just 'a'
Just 9
-- 反対の定義(第二引数優先)のLast a型もData.Monoidに存在
import Data.Monoid
print (getLast . mconcat . map Last $ [Nothing, Just 9, Just 10])
print (getLast $ Last (Just "one") `mappend` Last (Just "two"))
Just 10
Just "two"
モノイドで畳み込むいろんなデータ構造の上に畳み込みを定義したい
畳み込みはリスト以外に対しても可能
木構造
Foldable型クラス
畳み込みできることを表す型クラス
Preludeの関数と衝突するためData.Foldableモジュールの
中にある
import qualified Data.Foldable as F
print "type of foldr"
:type foldr
-- Data.Foldableのfoldrは畳み込みができる型ならリストに限らずなんでも畳み込める
print "type of F.foldr"
:type F.foldr
-- 実例
print (foldr (*) 1 [1,2,3])
print (F.foldr (*) 1 [1,2,3])
-- Maybeも可能
print (F.foldl (+) 2 (Just 9))
print (F.foldr (||) False (Just True))
Use product
Found: foldr (*) 1 Why Not: product
"type of foldr"
foldr :: forall a b. (a -> b -> b) -> b -> [a] -> b
"type of F.foldr"
F.foldr :: forall (t :: * -> *) a b. Foldable t => (a -> b -> b) -> b -> t a -> b
6
6
11
True
型コンストラクタをFOLDABLE
のインスタンスにする方法1つの方法は直接foldrを実装する
もっと簡単な方法
foldMap関数を実装
foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m
第一引数はFoldableにしたいコンテナの中身(a)の型の値を
取って、モノイドを返す関数
第二引数はa型の値を含むFoldable構造自身
foldMapの動作
第二引数の構造体に第一引数の関数をmapして構造体の中
身をモノイド値に変える
次にそれらのモノイド値をmappendして単一のモノイド値
へと結合
import qualified Data.Foldable as F
import Data.Monoid
-- 木構造の定義
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)
:type F.foldMap
-- TreeをFoldableのインスタンスにする
-- (a -> m)の結合関数は引数で与えるので
-- どのように結果を結合するかを実装では指定する
-- 結合の順番が大切なので以下が唯一の実装ではない
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
-- 使ってみる
testTree = Node 5
(Node 3
(Node 1 EmptyTree EmptyTree)
(Node 6 EmptyTree EmptyTree)
)
(Node 9
(Node 8 EmptyTree EmptyTree)
(Node 10 EmptyTree EmptyTree)
)
print (F.foldl (+) 0 testTree)
print (F.foldl (*) 1 testTree)
-- 木の中に3に等しい数があるか調べる
print (getAny $ F.foldMap (x -> Any $ x == 3) testTree)
-- 15より大きい数があるか
print (getAny $ F.foldMap (x -> Any $ x > 15) testTree)
-- Treeをリストに変換
print (F.foldMap (x -> [x]) testTree)
Use :
Found:  x -> [x] Why Not: (: [])
F.foldMap :: forall (t :: * -> *) a m. (Monoid m, Foldable t) => (a -> m) -> t a -> m
42
64800
True
False
[1,3,6,5,8,9,10]
IHASKELLとはipythonというpythonの高機能シェルの言語処理系をhaskell
に変えたもの
ipythonにはipython notebookというブラウザ上で対話的に
実行できる環境があるため、ihaskell notebookを使うとブ
ラウザ上でhaskellが対話的に実行可能
特徴コードだけでなくmarkdownも数式も書ける
ihaskell viewコマンドを使えばhtmlやtexなどにコンバート
可能
ipython notebookとは違ってreveal.jsを使ったslideにはコ
ンバートできない模様
ihaskell view markdown ipynb_nameでmarkdownにし
た後で適当な方法でslideにするのが良い
slideの区切りとハイライト指定は自分でやる必要有
大阪PRML読書会ではipython notebookでプレゼンなどして
います
このスライドの作り方1. iHaskell notebookでブラウザ上で対話的に記録
2. ihaskell view markdown chapter12_monoidを実行し
markdownに変換
chapter12_monoidは.ipynbの名前
3. markdownを手直し(ハイライト部分の指定など)
4. pandocでreveal.js用にhtmlを出力
pandoc -s -f markdown -t revealjs -V theme:beige
-o slide.html chapter12_monoid.md
5. reveal.jsの機能でPDFに変換
参考: https://gist.github.com/uupaa/7744760

すごいHaskell楽しく学ぼう-第12章モノイド-