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

时间:2016-01-13 17:10:27

标签: haskell io functional-programming

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

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)

现在,playerAnswer的类型为IO String,所以我必须在isCorrect区块内调用do吗?还有另一种方法可以将IO String解析为String吗?

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

3 个答案:

答案 0 :(得分:6)

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

  

现在,playerAnswer的类型为IO String,所以我必须在do块中调用isCorrect吗?

是的,如果你继续这门课程。

  

还有另一种方法可以将IO String解析为String吗?

没有不安全的。 IO String会为String提供String,但IO必须保留String

  

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

如果您不提前进行测量,可能会发生这种情况。但是,让我们从自上而下的方法来解决这个问题。首先,让我们介绍一些类型别名,以便明确我们是否将> type Text = String > type Answer = String > type Name = String > type Points = Int -- points are usually integers 作为答案或名称来查看:

> data Question = Question { answer :: Answer
>                          , text   :: Text } deriving Show
> data Player   = Player { name   :: Name
>                        , points :: Points } deriving Show

您的原始类型保持不变:

> gameTurn :: Question -> Player -> IO Player
> gameTurn q p = do
>    askQuestion q
>    a <- getAnswer
>    increasePointsIfCorrect q p a

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

askQuestions

这足以让你在一个回合中填满你的游戏。让我们用生命来填补这些功能。 getAnswerIO改变世界:他们在终端上打印一些内容并要求用户输入。他们> askQuestion :: Question -> IO () > askQuestion q = putStrLn (text q) > getAnswer :: IO String > getAnswer = getLine 某处:

increasePointsIfCorrect

在我们实际定义IO之前,让我们再次以稍微抽象的方式思考一个不使用> increasePointsIfCorrect' :: Question -> Player -> Answer -> Player > increasePointsIfCorrect' q p a = > if isCorrect q a > then increasePoints p > else p 的版本:

increasePointsIfCorrect'

顺便说一句,如果你仔细观察,你会发现> increasePoints :: Player -> Player > increasePoints (Player n p) = Player n (p + 1) > isCorrect :: Question -> Answer -> Bool > isCorrect q a = answer q == a 实际上只是一个游戏回合。毕竟,它会检查答案并增加积分。说到:

IO

我们现在定义了几个不使用increasePointsIfCorrect的功能。所有缺失的都是> increasePointsIfCorrect :: Question -> Player -> Answer -> IO Player > increasePointsIfCorrect q p a = return (increasePointsIfCorrect' q p a)

> 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

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

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. 的情况下测试所有逻辑,这很好。例如:

playGame :: [Question] -> Player -> IO ()

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

锻炼

  • 告诉玩家他的回答是否正确。
  • 添加Threshold = 1000 Rate 1 (2 consecutive) = 100 Rate 2 (> 2 consecutive) = 200 Id DateTime Import Export Total 1 2016-01-13 00:00 1000 500 1500 2 2016-01-13 00:15 2500 100 3000 3 2016-01-13 00:30 1900 200 2100 4 2016-01-13 01:00 900 100 1200 ,它会一个接一个地问几个问题并告诉玩家最终得分。
  • 向玩家询问他/她的名字并将其存储在初始玩家中。
  • (初学者非常难)尝试找到一种方法,以便您可以自动玩游戏(例如测试),或者#34;反对&#34;一个人。提示:寻找&#34;域特定语言&#34;。

答案 1 :(得分:1)

作为替代方案,您也可以宣传 answerQuestion一个动作:

answerQuestion ::  Question -> Player -> IO Player 
answerQuestion question player =
    answer <- playerAnswer
    if isCorrect question answer
    then return $ Player (name player) (points player + 1)
    else return player
    where
      playerAnswer = do
          putStrLn $ text question
          getLine

所以是的,您可以说在这种情况下,您应该从isCorrect块中调用do

答案 2 :(得分:0)

  

还有另一种方法可以将IO String解析为String吗?

不,没有安全的方法可以将IO String转变为String。这是IO类型的

  

现在,playerAnswer的类型为IO String,所以我必须在isCorrect区块内调用do吗? [...]如果是第一次我感觉失去了函数式编程的所有好处,因为我最终会在do块中编写我的整个代码以便访问String

这就是它起初的样子,但它不是那样的。诀窍是我们使用适配器功能来连接纯净和有效的世界。例如,假设你有:

  • 问题question :: IO Question
  • 答案answer :: IO String

您想在isCorrect :: Question -> String -> BoolQuestion上致电String。一种方法是使用liftA2函数:

import Control.Applicative (liftA2)

example :: IO Bool
example = liftA2 isCorrect question answer
  where question :: IO Question
        question = _
        answer :: IO String
        answer = _

liftA2有这种通用类型:

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

在这个例子中,我们将它用于:

  • f := IO
  • a := Question
  • b := String
  • c := Bool

所以liftA2做的是适应一个纯函数来处理副作用类型。这就是Haskell编程通常的工作方式:

  1. 您尝试将大部分代码编写为纯函数
  2. 您使用liftA2之类的辅助功能来调整它们以使用不纯效果。
  3. 需要一些学习和练习才能掌握这一点。