読者です 読者をやめる 読者になる 読者になる

IT練習ノート

IT関連で調べたこと(実際は嵌ったこと)を書いています。

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.hs

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に定義されているはず。(ここで親クラスに定義されているみたいね、と思ってしまうのは、何かに毒されているのでしょうか?)

GHC/Base.lhs

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'

GHC.Err.errorを探すと、

GHC/Exception.lhs

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関数に与えています。

GHC/Exception.lhs

-- |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関数は:

GHC/Exception.lhs

-- | 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> 

ここまで書いてMonadチュートリアルにfailがほとんど出てこないのが分かった気がしてきた。