注意:这篇文章是用 literate Haskell 写的。您可以将其保存为 Game.lhs 并在您的 GHCi 中尝试。
现在 playerAnswer 的类型为 IO String,所以我必须在 do 块内调用 isCorrect 吗?
是的,如果你坚持那个课程。
还有其他方法可以将IO String 解析为String?
没有不安全的。 IO String 是给你一个String 的东西,但无论使用String 都必须留在IO。
在第一次的情况下,我感觉失去了函数式编程的所有好处,因为我最终会在 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 和 getAnswer 改变世界:他们在终端上打印一些东西并要求用户输入。他们必须在某个时候进入IO:
> 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 (),它会一个接一个地问几个问题,告诉玩家最终得分。
- 询问玩家他/她的名字并将其存储在初始玩家中。
- (对于初学者来说很难)尝试找到一种方法,以便您可以自动玩游戏(例如用于测试)或“对抗”人类。提示:寻找“领域特定语言”。