PreludeのfailはMonadFailのfailではない

Posted on 2017/08/13
Tags: [Haskell]

困ったこと

 僕はよく失敗表現の型を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する

 これはnegativeContextPreludefailを呼んでしまっていることに起因する。

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

ひどい話だ。



この記事はこちらから修正リクエストを送ることができます。
PreludeのfailはMonadFailのfailではない - github
ゴミ箱ボタンの左にある、鉛筆ボタンを押してね!