12 章 モノイド


HATATANI Shinta(@apstndb)




    November 18, 2012




                            1 / 59
自己紹介



   @apstndb
   千葉工業大学@津田沼 修士課程
   好きな言語は C++
       BoostCon 2011 によると「Haskell は C++ TMP のための擬
       似言語]
       http://boostcon.boost.org/program/sessions#milewski-
       haskell-the-pseudocode-language-for-c-template-
       metaprogramming
       乗るしかないこのビッグウェーブ
       目標は光と闇が両方そなわり最強に見える感じ




                                                              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. 左辺の関数を同じ位置にある右辺の値に適用
    既にリストはアプリカティブなので,区別するために ZipList
    a 型 (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 | Accountant
  data Race = Human | Elf | Orc | Goblin
  data PlayerCharacter = PlayerCharacter Race Profession




                                                           7 / 59
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
コンストラクタの型


 CharList :: [Char] -> CharList

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

 getCharList :: CharList -> [Char]

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

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




                                     9 / 59
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
タプルを 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
この時 fmap の型は

fmap :: (a -> b) -> Pair c a -> Pair c b

class 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
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 -> String
helloMe (CoolBool _) = "hello"

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

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

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




                                                   14 / 59
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
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] }

   CharList と [Char] を++で連結することは不可
   2 つの CharList を++で連結することも不可
       CharList はリストを含んでもリストではない!
   値コンストラクタ名とフィールド名が型の相互変換関数
   型クラスは引き継がない.deriving するか宣言する
   値コンストラクタもフィールドも 1 つの data は newtype に


data 自作の新しいデータ型を作る

   フィールドとコンストラクタを複数持つデータ型を作れる
       リスト, Maybe, 木, etc…
                                                        17 / 59
12.1 のまとめ



    型シノニム (type)
       型シグネチャの整理や型名が体を表すようにしたい
    newtype
       既存の方をある型クラスのインスタンスにしたい
    data
       何かまったく新しいものを作りたい




                                 18 / 59
Section 2

12.2 Monoid 大集合 (p.265)




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




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

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




                            20 / 59
こんな型はどうだろう?
    *は 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
共通の性質
    関数の引数は 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
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
モノイド則 (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
12.2 のまとめ




  モノイドは

    同じ型の 2 つの値からその型の 1 つの値を作る 2 項演算
    モノイド則を守る必要がある
      単位元
      結合的




                                      25 / 59
Section 3

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




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




  instance Monoid [a] where
      mempty = []
      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 つある

      *を演算にして 1 を単位元にする
      +を演算にして 0 を単位元にする

  ghci>   0 + 4
  4
  ghci>   5 + 0
  5
  ghci>   (1 + 3) + 5
  9
  ghci>   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) where
      mempty = Product 1
      Product x `mappend` Product y = Product (x * y)




                                                        31 / 59
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
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
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
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
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
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
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
Ordering のモノイド則の確認



  ghci>   LT `mappend` GT
  LT
  ghci>   GT `mappend` LT
  GT
  ghci>   mempty `mappend` LT
  LT
  ghci>   mempty `mappend` GT
  GT




                                39 / 59
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
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
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
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
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
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
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
余談 単位元が無かったら?



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

       Q リストの畳み込みだけなら mempty が無くてもでき
         るのでは?
       A foldr1 のように空リストの場合にエラーを吐くか,
         結果の型が Maybe m になる

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




                                           47 / 59
12.3 のまとめ

    今まで見てきた型にもモノイドがある
    newtype で複数のインスタンスを持つ場合も
      リスト
             ZipList
      Num
             Sum
             Product
      Bool
             Any
             All
      Ordering
      Maybe
             First
             Last

                               48 / 59
Section 4

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




                         49 / 59
Foldable



       リストが畳み込めるのは分かった.
            リスト以外のデータ構造は畳み込めないの?

   それ Foldable でできるよ!

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

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




                                                                50 / 59
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
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
7 章の木構造を Foldable にしてみよう



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

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

     ある型コンストラクタを Foldable のインスタンスにするには
     foldr か foldMap を実装すれば良い
         foldMap 関数の方が簡単




                                                       53 / 59
foldMap とは




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

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




                                                        54 / 59
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
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
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
蛇足




     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
12.4 のまとめ




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




                                    59 / 59

すごいH 第12章モノイド

  • 1.
    12 章 モノイド HATATANIShinta(@apstndb) November 18, 2012 1 / 59
  • 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.
  • 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.
    ZipList はどう実装する? 別の型を作る必要がある 1. data を使う data ZipList a = ZipList [a] 値を取り出すにはパターンマッチを使う 2. data のレコード構文を使う data ZipList a = ZipList { getZipList :: [a] } リストを取り出す getZipList 関数が手に入る ある型の型クラスにするには deriving と instance を使う 5 / 59
  • 6.
    newtype 3.newtype を使う newtype は「1 つの型を取り,それを何かにくるんで別の型に見 せかける」ことに特化 実際の ZipList a の定義 newtype ZipList a = ZipList { getZipList :: [a] } Q data の代わりに newtype にしただけだけど何が良 くなるの? A data はコンストラクタに包む時も解く時もランタ イムのオーバーヘッドがある newtype はコンパイル時のみ別の型として扱われ, ランタイムにオーバーヘッドが無い 6 / 59
  • 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.
    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.
    コンストラクタの型 CharList ::[Char] -> CharList [Char](文字列) を受け取って CharList を返す getCharList :: CharList -> [Char] CharList を受け取って [Char](文字列) を返す 解釈 1. 包んだりほどいたり 2. 2 種類の型間の変換 9 / 59
  • 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.
    タプルを 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.
    この時 fmap の型は fmap:: (a -> b) -> Pair c a -> Pair c b class 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.
    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.
    例えば data に対してパターンマッチをする関数を作る dataCoolBool = CoolBool { getCoolBool :: Bool } helloMe :: CoolBool -> String helloMe (CoolBool _) = "hello" ghci> helloMe undefined "*** Exception: Prelude.undefined data で定義した型には複数の値コンストラクタがありうる (CoolBool _) にマッチするか確認できるところまで評価が 必要 undefined を評価してしまう 14 / 59
  • 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.
    type vs. newtypevs. 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.
    newtype 既存の型から新しい型を作る 型クラスのインスタンスを作るために使用 newtype CharList= CharList { getCharList :: [Char] } CharList と [Char] を++で連結することは不可 2 つの CharList を++で連結することも不可 CharList はリストを含んでもリストではない! 値コンストラクタ名とフィールド名が型の相互変換関数 型クラスは引き継がない.deriving するか宣言する 値コンストラクタもフィールドも 1 つの data は newtype に data 自作の新しいデータ型を作る フィールドとコンストラクタを複数持つデータ型を作れる リスト, Maybe, 木, etc… 17 / 59
  • 18.
    12.1 のまとめ 型シノニム (type) 型シグネチャの整理や型名が体を表すようにしたい newtype 既存の方をある型クラスのインスタンスにしたい data 何かまったく新しいものを作りたい 18 / 59
  • 19.
  • 20.
    型クラスは同じ振る舞いをする型のインターフェース Eq 等号が使える Ord 順序が付けられる Functor, Applicative … 新しい型を作る時は欲しい機能の型クラスを実装する 20 / 59
  • 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.
    共通の性質 関数の引数は 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.
    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.
    モノイド則 (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.
    12.2 のまとめ モノイドは 同じ型の 2 つの値からその型の 1 つの値を作る 2 項演算 モノイド則を守る必要がある 単位元 結合的 25 / 59
  • 26.
  • 27.
    リストはモノイド (p.268) instance Monoid [a] where mempty = [] mappend = (++) リストは中身の型に関わらず Monoid 27 / 59
  • 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.
    モノイド則に交換法則はない モノイド則は a`mappend` b = b `mappend` a を要求しない 交換法則は*は満たすが殆どのモノイドは満たさない! ghci> "one" `mappend` "two" "onetwo" ghci> "two" `mappend` "one" "twoone" 29 / 59
  • 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.
    Product の定義とインスタンス宣言 inData.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.
    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.
    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.
    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.
    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.
    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.
    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.
    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.
    Ordering のモノイド則の確認 ghci> LT `mappend` GT LT ghci> GT `mappend` LT GT ghci> mempty `mappend` LT LT ghci> mempty `mappend` GT GT 39 / 59
  • 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.
    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.
    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.
    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.
    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.
    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.
    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.
    余談 単位元が無かったら? モノイドは主に畳み込みに使われる Q リストの畳み込みだけなら mempty が無くてもでき るのでは? A foldr1 のように空リストの場合にエラーを吐くか, 結果の型が Maybe m になる 空リストでも使える畳み込み mconcat :: [m] -> m にモノイド は必要十分 47 / 59
  • 48.
    12.3 のまとめ 今まで見てきた型にもモノイドがある newtype で複数のインスタンスを持つ場合も リスト ZipList Num Sum Product Bool Any All Ordering Maybe First Last 48 / 59
  • 49.
  • 50.
    Foldable リストが畳み込めるのは分かった. リスト以外のデータ構造は畳み込めないの? それ Foldable でできるよ! Functor: 関数で写せるものを表す型クラス Foldable: 畳み込みできるものを表す型クラス Data.Foldable Foldable 用の foldr, foldl, foldr1, foldl1 を含む 50 / 59
  • 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.
    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.
    7 章の木構造を Foldableにしてみよう data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show) を Foldable にすれば畳み込みが可能になる. ある型コンストラクタを Foldable のインスタンスにするには foldr か foldMap を実装すれば良い foldMap 関数の方が簡単 53 / 59
  • 54.
    foldMap とは foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m a -> m: 中身からモノイドへ変換する関数 t a: Foldable な構造 m: 結果のモノイド値 構造の中身をモノイド値に変換してから畳み込む関数 54 / 59
  • 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.
    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.
    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.
    蛇足 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.
    12.4 のまとめ Foldable を実装すると 任意の構造で fold ファミリが使えるようになる! 構造からリストを作るのも簡単 59 / 59