読者です 読者をやめる 読者になる 読者になる

すごいHaskell読書会に参加しました

すごいHaskell読書会 in 大阪 2週目 #14 : ATND https://atnd.org/events/62373

「すごい Haskell 読書会 in 大阪」2周目の第1回から参加して、ついに第14回になりました。今日は「第12章 モノイド」の回でした。以前 Haskell を独学でやろうとしていた時にはこの辺りの話にたどり着く前に挫折してしまっていたのですが、無事に勉強が続いていて、読書会すばらしいです。

ファンクターとかモノイドとかが出てくると、数学っぽい感じがしてきて楽しくなります。

むかし数学を勉強していたときは、モノイドは知識としては知っていたものの、ひとつの演算に関する単位元の存在と結合則だけの世界のいったい何が面白いのか分かっていませんでした。そんなモノイドの性質がプログラミングに効いてくるなんて、実に楽しいですね。

そんな気持ちもあって、今日の章はわりとすんなり入っていけた章でした。

「第12章 モノイド」の内容

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

この節ではまだモノイドが出てこなくて、newtype キーワードの話です。正直言ってあんまり魅かれない内容で退屈ですが、あとでモノイドの例を見るときに使うので仕方がない、という感じです。

ざっくりまとめると次のようになります。

  • type キーワード : 既存の型に別名をつける。新しい型ができるわけではない。
  • newtype キーワード : 既存の型を包んで新しい型を作る。値コンストラクタ1つとフィールド1つだけ。
  • data キーワード : 新しい型を作る。値コンストラクタもフィールドも好きなように作れる。

12.2 Monoid 大集合

いよいよ Monoid 型クラスの登場です。

ある型が Monoid 型クラスのインスタンスになるには、memptymappend の2つを定義する必要があります。mempty単位元mappend二項演算子です。本のなかでは mappend の命名が良くないと指摘していました。まあ良くないのはそうなんですが、そう命名した気持ちは分かる気がします。ていうか、mempty の命名には突っ込まないのかな。

Monoid 型クラスにはもうひとつ、mconcat 関数がデフォルト実装込みで定義されています。モノイドのリストが与えられたとき、その要素をすべて演算した結果を返します。この mconcat が使えるのが Monoid の利点です。

それと、モノイド則。Monoid 型クラスのインスタンスはこれを満たすように作ります。とはいっても、Haskell はこれを満たすかどうかチェックしてくれないので、自分でチェックしないといけませんが。モノイド則のうちの結合則のおかげで、foldlfoldr で演算結果が変わらないのはモノイドの特徴です。

12.3 モノイドとの遭遇

まずは当たり前っぽいやつら。

  • リストはモノイドになります。
  • 数は、2通りの方法でモノイドになります。ここで newtype キーワードを活用して Product と Sum という新しい型を作ります。
    • Product a は単位元 1 と演算 * でモノイド。
    • Sum a は単位元 0 と演算 + でモノイド。
  • Bool も同様に2通りの方法があります。

わりと有益なのが以下。

  • Ordering 型はモノイドになります。こいつの定義がうまくて、比較関数を mappend を使ってきれいに定義できるようになっています。
  • Maybe a 型はモノイドになります。ここで複数の方法があるのがポイント。特に newtype キーワードで定義される First と Last が案外役に立ちます。mconcat で Maybe 型のリストの中の Just 値を簡単に取り出せます。

12.4 モノイドで畳み込む

Foldable 型クラスというのが出てきます。これを定義するときは foldMap 関数を定義するのが簡単ですが、こいつがモノイドを活用しています。本には木構造を Foldable にする例が出ていました。

ところで、これについての本に記載の定義がどうも不自然な気がします。

import Data.Monoid
import Data.Foldable as F

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

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

右辺の F.foldMap は単に foldMap なんじゃないかと。ここでわざわざ Data.Foldable の foldMap を持ち出す必要なんてない気がします。しかし、読書会の場ではそうするとおかしくなるという報告をいただいて、あれれ? そうなの? ということで引き下がりました。

しかし、僕の手元で再度試してみたら、右辺を単に foldMap にして、問題ありませんでした。まあ僕はこれで納得したので良いのですが、読書会の場での話はどういうことだったのだろう?

まとめ

読書会も残りあと数回になってきました。

読書会が終わったら次は何をするか考えないと。もうちょっと Haskell でいろいろやりたいけど、何がいいかなあ。