Monadのfail関数について思ったこと
Monadのチュートリアルでほとんど語られることがないfailについて確認します。
Monadの情報を確認すると:
Prelude> :i Control.Monad Top level: Failed to load interface for `Control' Use -v to see a list of the files searched for. Prelude> :i Monad class Monad m where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return :: a -> m a fail :: String -> m a -- Defined in `GHC.Base' instance Monad Maybe -- Defined in `Data.Maybe' instance Monad (Either e) -- Defined in `Data.Either' instance Monad [] -- Defined in `GHC.Base' instance Monad IO -- Defined in `GHC.Base' instance Monad ((->) r) -- Defined in `GHC.Base'
ここに、fail :: String -> m a
という定義があります。形式的には、returnに似ていますが、fail関数は、第一パラメータの型がStringと明示的になっています。
早速使ってみます。
Prelude> fail "abc" *** Exception: user error (abc) Prelude>
失敗させたからExceptionが表示されています。まず、この時点で?となりました。 そもそも、これで動作してしまうのは、おかしいのではないかと。どの型のfailか指定していないのに、なんでこうなるのかと。
わかってしまえば簡単で、Prelude上で動作させているので、IOモナドと解釈されています。このことは後で確認します。
では、Maybeを指定して実行してみると。
Prelude> fail "abc" :: Maybe <interactive>:63:15: Expecting one more argument to `Maybe' In an expression type signature: Maybe In the expression: fail "abc" :: Maybe In an equation for `it': it = fail "abc" :: Maybe Prelude>
おっと、Maybe自体は具体型ではなかった。 なので:
Prelude> Prelude> fail "abc" :: Maybe String Nothing Prelude>
となり、Nothingが帰ってきます。定義を確認すると:
instance Monad Maybe where (Just x) >>= k = k x Nothing >>= _ = Nothing (Just _) >> k = k Nothing >> _ = Nothing return = Just fail _ = Nothing
ですから、fail _ = Nothing
なので、failに何を与えてもNothingと。
Prelude> fail "abcd" :: Maybe String Nothing Prelude> fail "abcde" :: Maybe String Nothing
確かに、何を与えてもNothing
ですね。まぁ、Maybe自体が失敗を含んだ概念なので、fail関数自体が冗長なのでしょう。
あと、具体型をいろいろ変えてみても結果は同じです:
Prelude> fail "abc" :: Maybe Int Nothing Prelude> fail "abc" :: Maybe Char Nothing Prelude>
それでは、別の型も確認してみます。
Either:
Prelude> fail "abc" :: Either String String *** Exception: abc
Eitherモナドの定義を確認すると、
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show, Typeable) instance Functor (Either a) where fmap _ (Left x) = Left x fmap f (Right y) = Right (f y) instance Monad (Either e) where return = Right Left l >>= _ = Left l Right r >>= k = k r
failの定義はありませんでした!
ということはMonadに定義されているはず。(ここで親クラスに定義されているみたいね、と思ってしまうのは、何かに毒されているのでしょうか?)
class Monad m where -- | Sequentially compose two actions, passing any value produced -- by the first as an argument to the second. (>>=) :: forall a b. m a -> (a -> m b) -> m b -- | Sequentially compose two actions, discarding any value produced -- by the first, like sequencing operators (such as the semicolon) -- in imperative languages. (>>) :: forall a b. m a -> m b -> m b -- Explicit for-alls so that we know what order to -- give type arguments when desugaring -- | Inject a value into the monadic type. return :: a -> m a -- | Fail with a message. This operation is not part of the -- mathematical definition of a monad, but is invoked on pattern-match -- failure in a @do@ expression. fail :: String -> m a {-# INLINE (>>) #-} m >> k = m >>= \_ -> k fail s = error s
デフォルト実装は`error
関数のようです。
Prelude> :i error error :: [Char] -> a -- Defined in `GHC.Err'
module GHC.Err ( absentErr -- :: a , divZeroError -- :: a , overflowError -- :: a , error -- :: String -> a , undefined -- :: a ) where ... error :: [Char] -> a error s = throw (ErrorCall s)
とErrorCallに包んで、throw関数に与えています。
-- |This is thrown when the user calls 'error'. The @String@ is the -- argument given to 'error'. newtype ErrorCall = ErrorCall String deriving (Eq, Ord, Typeable)
throw関数は:
-- | Throw an exception. Exceptions may be thrown from purely -- functional code, but may only be caught within the 'IO' monad. throw :: Exception e => e -> a throw e = raise# (toException e)
となっており、IOモナドとして処理されるようです。raise#となっているのは、深入りはやめて、こっから先はCの世界に入るのでしょうか。
話が、だいぶそれましたが、Etiherモナドのfail
関数はデフォルト実装を使っているということでした。
次は、リスト([])
Prelude> fail "abc" :: [String] [] Prelude> fail "abc" :: [Int] [] Prelude>
どこかで聞いたことがあるような、ないような。非決定性の失敗は、要素ゼロとするということですね。
次、IOモナド
Prelude> fail "abc" :: IO String *** Exception: user error (abc) Prelude> fail "abc" :: IO Int *** Exception: user error (abc) Prelude>
これが、最初にやった、型を指定していないときと同じですね。
最後に、関数(->)
Prelude> fail "abc" :: (String -> String) <interactive>:110:1: No instance for (Show (String -> String)) arising from a use of `print' Possible fix: add an instance declaration for (Show (String -> String)) In a stmt of an interactive GHCi command: print it
おっと、表示できないですよと。
基本に立ち返り、
fail String -> m a
だから、fail String -> ((->) r) a
で、fail String -> (r -> a)
なので、関数そのものが返却されるからprintしようがないのですね。
一回バインドした上で、関数適用してみると。
Prelude> let e = fail "abc" :: (String -> String) Prelude> e "xyz" "*** Exception: abc Prelude>
確かに、エラー文字列が出てきました。"*** Exception: abc
と、なぜか、先頭にだぶるクォーテーションが出てくるのは、なんか意味あるんでしょうか。バグのような気もしますが。
((->) r)
のfail
関数場合、関数といいつつも、結果は、実際の結果側の型にはならず、IOモナドとして処理されるようです。下記の例では、作った関数は、Charの結果を期待しますが、関数適用させると、Char型のデータは帰ってきません。
Prelude> let e = fail "abc" :: (Int -> Char) Prelude> e "abc" <interactive>:125:3: Couldn't match expected type `Int' with actual type `[Char]' In the first argument of `e', namely `"abc"' In the expression: e "abc" In an equation for `it': it = e "abc" Prelude> e 1 *** Exception: abc Prelude> e 1 *** Exception: abc Prelude>