嗨,我有一个非常的菜鸟问题,让我说当你必须回答问题时我想创建一个游戏,我写了这个
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
值
答案 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
这足以让你在一个回合中填满你的游戏。让我们用生命来填补这些功能。 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 ()
完全实现这些测试超出了这个问题的范围。
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
,它会一个接一个地问几个问题并告诉玩家最终得分。 答案 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 -> Bool
和Question
上致电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编程通常的工作方式:
liftA2
之类的辅助功能来调整它们以使用不纯效果。需要一些学习和练习才能掌握这一点。