如何在Haskell中正确处理IO返回值

Alb*_*zon 1 io haskell functional-programming

嗨,我有一个非常noob的问题,让我说我想创建一个游戏,当你必须回答问题时,我写了这个

data Question = Question { answer::String, text::String }
data Player = Player { name::String, points::String }

answerQuestion ::  Question -> Player -> Player 
answerQuestion question player
    | isCorrect question playerAnswer = Player (name player) (points player + 1)
    | otherwise = player
    where
      playerAnswer = do
          putStrLn text(question)
          getLine

isCorrect ::  Question -> String -> Bool 
isCorrect question try = try == answer(question)
Run Code Online (Sandbox Code Playgroud)

现在playerAnswer有类型IO String所以我必须isCorrectdo块内调用吗?有另一种方式来解析IO StringString

在第一种情况下,我感觉失去了函数式编程的所有好处,因为我最终会在do块中编写我的整个代码以便访问String

Zet*_*eta 6

注意:这篇文章是用文字Haskell编写的.您可以将其保存为Game.lhs并在GHCi中尝试.

现在playerAnswer有类型IO String所以我必须在do块内调用isCorrect吗?

是的,如果你坚持这个课程.

有另一种方式来解析IO StringString

没有,这不是不安全的.一个IO String东西可以给你一个String,但无论用什么String用来留下来IO.

在第一种情况下,我感觉失去了函数式编程的所有好处,因为我最终会在do块中编写我的整个代码以便访问String值.

如果您不提前进行测量,可能会发生这种情况.但是,让我们从自上而下的方法来解决这个问题.首先,让我们介绍一些类型别名,以便我们清楚地看到一个Stringin作为答案或名称:

> type Text   = String
> type Answer = String
> type Name   = String
> type Points = Int    -- points are usually integers
Run Code Online (Sandbox Code Playgroud)

您的原始类型保持不变:

> data Question = Question { answer :: Answer
>                          , text   :: Text } deriving Show
> data Player   = Player { name   :: Name
>                        , points :: Points } deriving Show
Run Code Online (Sandbox Code Playgroud)

现在让我们考虑一下游戏.你想问问玩家一个问题,得到他的答案,如果他是对的,加上一些观点:

> gameTurn :: Question -> Player -> IO Player
> gameTurn q p = do
>    askQuestion q
>    a <- getAnswer
>    increasePointsIfCorrect q p a
Run Code Online (Sandbox Code Playgroud)

这足以让你在一个回合中填满你的游戏.让我们用生活来填补这些功能.askQuestionsgetAnswer改变世界:他们在终端上打印一些东西并要求用户输入.他们必须要在IO一些点:

> askQuestion :: Question -> IO ()
> askQuestion q = putStrLn (text q)

> getAnswer :: IO String
> getAnswer = getLine
Run Code Online (Sandbox Code Playgroud)

在我们实际定义之前increasePointsIfCorrect,让我们再考虑一个不再使用的版本IO:

> increasePointsIfCorrect' :: Question -> Player -> Answer -> Player
> increasePointsIfCorrect' q p a =
>    if isCorrect q a
>       then increasePoints p
>       else p
Run Code Online (Sandbox Code Playgroud)

顺便说一句,如果你仔细观察,你会注意到这increasePointsIfCorrect'实际上只是一场比赛.毕竟,它会检查答案并增加积分.说起:

> increasePoints :: Player -> Player
> increasePoints (Player n p) = Player n (p + 1)

> isCorrect :: Question -> Answer -> Bool
> isCorrect q a = answer q == a
Run Code Online (Sandbox Code Playgroud)

我们现在定义了几个不使用的函数IO.所有缺少的是increasePointsIfCorrect:

> increasePointsIfCorrect :: Question -> Player -> Answer -> IO Player
> increasePointsIfCorrect q p a = return (increasePointsIfCorrect' q p a)
Run Code Online (Sandbox Code Playgroud)

您现在可以通过一个简单的短游戏来检查:

> theQuestion = Question { text   = "What is your favourite programming language?"
>                        , answer = "Haskell (soon)"}
> thePlayer   = Player { name   = "Alberto Pellizzon"
>                      , points = 306 }
>
> main :: IO ()
> main = gameTurn theQuestion thePlayer >>= print
Run Code Online (Sandbox Code Playgroud)

还有其他方法可以解决这个问题,但我想这对初学者来说更容易.

无论哪种方式,最好的是我们现在可以在不使用的情况下测试所有逻辑IO.例如:

prop_increasesPointsOnCorrectAnswer q p =
   increasePointsIfCorrect' q p (answer q) === increasePoints p

prop_doesnChangePointsOnWrongAnswer q p a = a /= answer q ==>
   increasePointsIfCorrect' q p a === p

ghci> quickCheck prop_increasesPointsOnCorrectAnswer 
OK. Passed 100 tests.
ghci> quickCheck prop_doesnChangePointsOnWrongAnswer 
OK. Passed 100 tests.
Run Code Online (Sandbox Code Playgroud)

完全实现这些测试超出了这个问题的范围.

演习

  • 告诉玩家他的答案是否正确.
  • 添加playGame :: [Question] -> Player -> IO (),接下来问几个问题并告诉玩家最终得分.
  • 向玩家询问他/她的名字并将其存储在初始玩家中.
  • (对于初学者来说非常困难)尝试找到一种方法,以便您可以自动玩游戏(例如测试)或"反对"人类游戏.提示:寻找"特定领域语言".

  • 或者只是编写一些脚本来提取所有答案,将它们串在一起并发布"Haskell是对宇宙和其他所有编程的最终参考";) (4认同)