![](/img/trans.png)
[英]how to use haskell Control. Exception handle to return a IO(Maybe a)
[英]How to handle IO return values properly in Haskell
嗨,我有一個非常新手的問題,可以說我想在必須回答問題時創建一個游戲,我寫了這個
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
類型,所以我必須在do
塊內調用isCorrect
嗎? 還有另一種方法可以將IO String
解析為String
嗎?
在第一種情況下,我感覺失去了函數式編程的所有好處,因為我最終將在do
塊中編寫整個代碼,以便訪問String
值
注意:這篇文章是用識字的Haskell撰寫的。 您可以將其另存為Game.lhs並在GHCi中嘗試。
現在playerAnswer具有
IO String
類型,所以我必須在do塊內調用isCorrect嗎?
是的,如果您堅持那條路線。
還有另一種方法可以將
IO String
解析為String
嗎?
沒有什么不是不安全的。 IO String
可以為您提供一個String
,但是使用該String
任何方式都必須保留在IO
。
首先,我感覺失去了函數式編程的所有好處,因為我最終會在do塊中編寫整個代碼,以便訪問String值。
如果您不及早進行測量,可能會發生這種情況。 但是,讓我們從上至下的方法進行處理。 首先,讓我們介紹一些類型別名,以便我們清楚地將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 ()
,它接着又問幾個問題,並告訴玩家最終得分。 作為替代,你可以促進 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
所以是的,您可以說在這種情況下,您應該從do
塊內部調用isCorrect
還有另一種方法可以將
IO String
解析為String
嗎?
不,沒有將IO String
轉換為String
安全方法。 這是點 IO
型!
現在playerAnswer具有
IO String
類型,所以我必須在do
塊內調用isCorrect
嗎? [...]首先,我感覺失去了函數式編程的所有好處,因為我最終會在do塊中編寫整個代碼,以便訪問String
值
乍一看就是這樣,但事實並非如此。 訣竅是我們使用適配器函數來連接純凈的世界和有效的世界。 例如,假設您有:
question :: IO Question
answer :: IO String
然后您要在Question
和String
上調用isCorrect :: Question -> String -> Bool
。 一種方法是使用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
類的輔助功能來使其適應不純效果。 要掌握這一點,需要一些學習和實踐。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.