困ったこと
僕はよく失敗表現の型をMonadFail
インスタンスにして、fail
を使って失敗を抽象化するなどをするんだけど、
それに対するテストでfail
を使うと、何故かそのインスタンスのfail
が呼ばれず、IO
例外が送出されることがある。
Test.hs (テスト用のコード) vv
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Test
( ExceptionCause
, Result (..)
) where
import Control.Monad.Fail (MonadFail(..))
type ExceptionCause = String
newtype Result a = Result
{ unResult :: Either ExceptionCause a
} deriving (Show, Eq, Functor, Applicative, Monad)
instance MonadFail Result where
fail = Result . Left
Main.hs (テスト) vv
import Test (Result(..))
-- tasty-discover用の命名
test_it_is_pure :: Bool
test_it_is_pure = pureFail "x(" == negativeContext "x("
where
-- Expected behavior
pureFail :: String -> Result ()
pureFail = Result . Left
-- An alias of `Result`'s fail
negativeContext :: String -> Result ()
negativeContext = fail -- (!!)
main :: IO ()
main = print test_it_is_pure
このテストは通らない。
(以下の出力になる)
$ stack runghc Main.hs Test.hs
Main.hs: x(
Control.Monad.Failのfailを明示的にimportする
これはnegativeContext
でPrelude
のfail
を呼んでしまっていることに起因する。
Prelude.fail :: Monad m => String -> m a
Control.Monad.Fail.fail :: MonadFail m => String -> m a
テストを修正する。
Main.hs
import Control.Monad.Fail (fail)
import Prelude hiding (fail)
import Test (Result(..))
-- tasty-discover用の命名
test_it_is_pure :: Bool
test_it_is_pure = pureFail "x(" == negativeContext "x("
where
-- Expected behavior
pureFail :: String -> Result ()
pureFail = Result . Left
-- An alias of `Result`'s fail
negativeContext :: String -> Result ()
negativeContext = fail -- (!!)
main :: IO ()
main = print test_it_is_pure
出力
$ stack runghc Main.hs Test.hs
True
要するにMonadFailProposalより前の歴史のせい!