mike-neckのブログ

Java or Groovy or Swift or Golang

Haskellで基本的なことをhspecで確認してみる

上記のとおりです。

『すごいH』本を読みながら、リストだとかMaybeだとかEitherだとかがFunctorApplicativeなどを満たしていることを各法則を試すことによって確認しています。

具体的な課題はこんな感じ


課題

次の型がそれぞれ下に列挙された型クラスのインスタンスであることを示せ
(具体的には各型クラスの法則を満たすことを示せば良い)

  • [a]
    • Functor
    • Applicative
    • Monoid
    • Monad
    • Foldable
  • Maybe a
    • Functor
    • Applicative
    • Monoid
    • Monad
  • Either e a
    • Functor
    • Applicative
    • Monad
  • (->)
    • Functor
    • Applicative
    • Monad
    • Monoid
  • (a, b)
    • Monad(((,) a)Monad)
    • Monoid
  • Writer w a
    • Monad

なお、各型クラスの法則は以下のとおり

Functor

-- idで値を写した場合、ファンクター値が変化してはいけない
fmap id f == f
-- 二つの関数gとhについて、「gとhの合成関数で写したもの」と
-- 「hで写したファンクター値をgに適用したもの」とが一致しなければならない
fmap (g.h) f == (fmap g . fmap h) f

Applicative

-- Functorとのインターオペラビリティ
pure f <*> x == fmap f x
-- pure idを適用しても値が変化してはいけない
pure id <*> x == x
-- 合成順序を変えても結果が変わってはならない
pure (.) <*> u <*> v <*> w == u <*> (v <*> w)
-- pureでApplicativeにした値でpureでApplicativeにした値を写したものと、
-- 先に写したものをpureでApplicativeにしたものとは一致しなければならない
pure f <*> pure x == pure (f x)
-- $関数
u <*> pure y == pure ($ y) <*> u

Monoid

-- empty値にappendした場合、値が変化してはいけない
memempty `mappend` x == x
-- 値にempty値をappendした場合、値が変化してはいけない
x `mappend` mempty == x
-- 結合則
(x `mappend` y) mappend z == x `mappend` (y `mappend` z)

Monad

-- 左恒等性
-- returnでMonadにした値に関数をbindした結果は
-- 値を関数に適用したものと一致しなければならない
return x >>= f == f x
-- 右恒等性
-- returnをbindしたモナド値は変化してはいけない
m >>= return == m
-- 結合法則
-- モナド値をモナド関数に適用する式を評価するときに、入れ子の順序はどうでもよく、
-- ただ関数の意味だけが重要であるということ
m >>= f >>= g == m >>= (\x -> f x >>= g)

なお、参考までにListMonoidであることを確認するhpecはこんな感じ。

module List.MonoidSpec (
  spec
) where

import Test.Hspec.Core.Spec
import Test.Hspec.Expectations

import Data.Monoid
import Control.Monad (mapM_)

import List.TestData

spec :: Spec
spec = do
    describe "List satisfies Monoid law" $ do
        describe "law1 : mempty `mappend` x == x" $ do
            mapM_ (itSatisfies law1LF id) testLists
        describe "law2 : x `mappend` mempty == x" $ do
            mapM_ (itSatisfies law2LF id) testLists
        describe "law3 : mappend bounding : (x <> y) <> z == x <> (y <> z)" $ do
            mapM_ (itSatisfies law3LF law3RF) testLists

unit :: [a]
unit = mempty

law1LF :: [a] -> [a]
law1LF xs = unit <> xs

law2LF :: [a] -> [a]
law2LF xs = xs <> mempty

law3LF :: [Int] -> [Int]
law3LF xs = ([1] <> [2,3]) <> xs

law3RF :: [Int] -> [Int]
law3RF xs = [1] <> ([2,3] <> xs)

まあ、ただ純粋なあれだけをやってても、アウトプットにならないので、この証明が終わったら、Yesodとかやってみたいと思うのだけど、ちょっと趣味っぽいアプリも作りたいので、自分のインスタンス複数あればいいなと思ったりしている。

おわり