モナドの中に読むというイディオムがあります。
readM :: (Monad m, Read a) => String -> m a
readM s | [x] <- [x | (x, "") <- reads s] = return x
-- or @[x] <- [x | (x, _) <- reads s] = return x@
-- to allow the garbage at the end of parsed string
| otherwise = fail $ "Failed to parse: \"" ++ s ++ "\""
IO
モナドにとっては安全ではありません:
> readM "CmdOther" :: IO Cmd
CmdOther
> readM "Cmd?Other" :: IO Cmd
*** Exception: user error (Failed to parse: "Cmd?Other")
の場合は例外をfail
スローしますが、これは処理できます。IOError
IO
*Main> (readM "Cmd?Other" :: IO Cmd) `catch` const (return CmdOther)
CmdOther
そしてMaybe
モナドの場合は安全です:
> readM "CmdOther" :: Maybe Cmd
Just CmdOther
> readM "Cmd?Other" :: Maybe Cmd
Nothing
この場合だからfail
です。const Nothing
guiString2Cmd
とにかく、署名付きのトータル関数が必要な場合は、次のString -> Cmd
ように記述できますreadM
。
guiString2Cmd :: String -> Cmd
guiString2Cmd s | [x] <- [x | (x, "") <- reads s] = x
| otherwise = CmdExit
その後:
> guiString2Cmd "CmdOther"
CmdOther
> guiString2Cmd "Cmd?Other"
CmdExit
もう少し一般的なアプローチ。
*
種類の場合:
class Failable0 t where
fail0 :: t
readG0 :: (Failable0 t, Read t) => String -> t
readG0 s | [x] <- [x | (x, "") <- reads s] = x
| otherwise = fail0
それから:
instance Failable0 Cmd where
fail0 = CmdExit
* -> *
種類の場合:
class Failable f where
fail :: String -> f a
class Functor f => Pointed f where
pure :: a -> f a
readG :: (Failable f, Pointed f, Read a) => String -> f a
readG s | [x] <- [x | (x, "") <- reads s] = pure x
| otherwise = fail $ "Failed to parse: \"" ++ s ++ "\""