MaybeT/Maybe 和 IO:信息的故障安全读取

MaybeT/Maybe and IO: Failsafe reading of information

我正在尝试读取用户输入的信息并将其解析为类型 Person,它使用类型 Gender。为此,我使用以下代码:

data Person = Person String Int Gender String
data Gender = Male | Female | NotSpecified deriving Read

instance Show Gender where
    show Male = "male"
    show Female = "female"
    show NotSpecified = "not specified"

instance Show Person where
    show (Person n a g j) = "Person {name: " ++ n ++ ", age: " ++ show a ++ 
        ", gender: " ++ show g ++ ", job: " ++ j ++ "}"

readPersonMaybeT :: MaybeT IO ()
readPersonMaybeT = do
    putStrLn "Name?:"
    name <- getLine
    putStrLn "Age?:"
    ageStr <- getLine
    putStrLn "Gender?:"
    genderStr <- getLine
    putStrLn "Job?:"
    job <- getLine

    let newPerson = Person name (read ageStr) (read genderStr) job
    putStrLn $ show newPerson

现在我想让它更安全——为了实现这一点,我尝试使用 MaybeT monad。使用这个,我得到了这个代码:

readPersonMaybeT :: MaybeT IO ()
readPersonMaybeT = do
    lift $ putStrLn "Name?:"
    name <- lift getLine
    lift $ putStrLn "Age?:"
    ageStr <- lift getLine
    lift $ putStrLn "Gender?:"
    genderStr <- lift getLine
    lift $ putStrLn "Job?:"
    job <- lift getLine

    let newPerson = Person name (read ageStr) (read genderStr) job
    lift $ putStrLn "show newPerson"

它通过 GHCI 获得 compiles/loaded,但是当我尝试执行 readPersonMaybeT 函数时,我收到错误消息

No instance for (Data.Functor.Classes.Show1 IO) arising from a use of `print' In a stmt of an interactive GHCi command: print it

我该如何解决这个问题?编写这段代码时,我使用了关于 Monad Transformers 的wikibook

编辑:当我尝试用 runMaybeT 'run' 它时,它会被执行,但它根本不是故障安全的。例如,输入无意义的年龄仍然会产生类似

的输出

Person {name: 85, age: *** Exception: Prelude.read: no parse.

如果您仅在请求所有输入后才进行验证,我将只使用 IO monad 和 return 一个 Maybe:

import Text.Read
import Control.Monad.Trans.Maybe
import Control.Monad.IO.Class

askPerson :: IO (Maybe Person)
askPerson = do
  name <- putStr "Name? " >> getLine
  a <- putStr "Age? " >> getLine
  g <- putStr "Gender? " >> getLine
  return $ do age <- readMaybe a
              gender <- readMaybe g
              return $ Person name age gender

请注意我们如何在 return 语句中使用 Maybe monad。

如果你想在输入无效值后停止询问输入,我会使用 MaybeT --

askPersonT :: MaybeT IO Person
askPersonT = do
  name   <- liftIO $ putStr "Name? " >> getLine
  age    <- MaybeT $ fmap readMaybe $ putStr "Age? " >> getLine
  gender <- MaybeT $ fmap readMaybe $ putStr "Gender? " >> getLine
  return $ Person name age gender

doit = runMaybeT askPersonT

如果用户输入的年龄无效,他们将不会被要求输入性别。