MaybeT/Maybe和IO:信息的安全读取方式

5

我正在尝试阅读用户输入的信息并将其解析为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单子。使用它,我得到了以下代码:

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编译/加载,但当我尝试执行readPersonMaybeT函数时,出现了错误消息。

没有(Data.Functor.Classes.Show1 IO)的实例 来自使用`print'的互动GHCi命令的语句 : print it

如何解决这个问题?编写此代码时,我使用了有关Monad变换器的wikibook
编辑:当我尝试使用runMaybeT运行它时,它被执行了,但它并不安全。例如,输入无意义的年龄仍然会导致输出,如下所示:

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


你是如何“执行”它的?你使用了 runMaybeT 吗? - pdexter
不,我不知道我必须调用runMaybeT...但是我现在尝试了一下,它并没有解决真正的问题(请参见编辑部分)。 - zimmerrol
你应该展示给我们 Person 的定义。 - ErikR
你可能想使用readMaybe - pdexter
请注意,您正在将 do 块中的每个操作提取出来。您从未在实际的 Maybe 单子中执行任何操作。 - pdexter
哦,这是真的,但我该如何在Maybe单子内读取这些值呢?这样是不行的:gender <- maybeRead genderStr::Gender,因为我不知道如何正确地转换读取到的值。 - zimmerrol
1个回答

7

如果您只在请求了所有输入后才进行验证,我建议使用IO单子并返回一个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单子。
如果您希望在输入无效值后停止请求输入,则可以使用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

如果用户输入了无效的年龄,系统将不会要求其填写性别。

1
在第一个例子中,我认为 return (Person <$> pure name <*> readMaybe a <*> readMaybe g) 也很常用。在第二个例子中,我们也可以使用 applicative-style,但我更喜欢你写的方式,因为每一行都已经很不平凡了。 - chi
你评论中的“纯净”一词让我感到困惑。我们不是已经通过return和fmap使用了正确的上下文吗? - amalloy
哦,我明白了。我错过了readMaybe调用中发生的事情。 - amalloy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接