Haskell:如何在IO中检查运行时类型?

5

我正在学习Haskell的入门材料,尝试完成命令行上愚蠢的石头剪刀布实现。

我认为在输入上使用类型保护就足以让编译器相信输入是RPS类型,但不幸的是,它并不是这样。

如何告诉编译器输入数据是某种类型呢?


data RPS = Rock | Paper | Scissors

_shoot :: RPS -> RPS -> String
_shoot Rock Paper = "Paper beats rock, you win!"
_shoot Paper Rock = "Paper beats rock, you loose."
_shoot Rock Scissors = "Rock beats scissors, you loose."
_shoot Scissors Rock = "Rock beats scissors, you win!"
_shoot Paper Scissors = "Scissors beats paper, you win!"
_shoot Scissors Paper = "Scissors beats paper, you loose!"
_shoot Rock Rock = "Tie!"
_shoot Scissors Scissors = "Tie!"
_shoot Paper Paper = "Tie!"

isRPS :: String -> Bool
isRPS s = elem s ["Rock", "Paper", "Scissors"]

main :: IO ()
main = do
  putStrLn "Rock, Paper, or Scissors?"
  choice <- getLine
  if isRPS choice -- this was my idea but is apparently not good enough
    then putStrLn (_shoot choice Rock) 
--                        ^^^^^^
-- Couldn't match type ‘[Char]’ with ‘RPS’ Expected type: RPS Actual type: String
    else putStrLn "Invalid choice."

3
这是一种“布尔盲”,即当你手头有更多信息时,将你知道的信息简化为布尔类型。isRPS 返回一个布尔值(包含 true 或 false),而不是更具信息量的类型(包含 RPS 值或错误)。在这种情况下,Maybe RPS 就足够了。如果你有多个错误需要返回,有时候你会想要使用 Either SomeError RPS。在 Haskell 中,Bool 没有像其他一些语言那样频繁使用。 - chi
2个回答

10

您没有将choice(一个String类型)转换为RPS,或者更好的做法是转换成Maybe RPS类型:

readRPS :: String -> Maybe RPS
readRPS "rock" = Just Rock
readRPS "paper" = Just Paper
readRPS "scissors" = Just Scissors
readRPS _ = Nothing

如果输入有效(其中x是相应的RPS项),则我们将返回一个Just x,否则返回Nothing

然后我们可以这样实现:

import Data.Char(toLower)

main :: IO ()
main = do
    putStrLn "Rock, Paper, or Scissors?"
    choice <- getLine
    case <b>readRPS (map toLower choice)</b> of
        <b>Just rps</b> -> putStrLn (_shoot rps Rock) 
        Nothing -> putStrLn "Invalid choice."
    main

2
谢谢,Maybe 的使用场景非常完美 - 我应该早点看到。我刚刚注意到 rps 和 Rock 需要交换才能使游戏正常运行。 - RichardForrester

4
您已经接近成功了,您只需要使用read函数将用户输入的字符串转换为您的RPS数据类型。
首先要做的是将RPS声明为Read类型类的实例。这可以通过修改您的data声明来轻松完成:
data RPS = Rock | Paper | Scissors deriving Read
deriving Read的作用是给RPS默认实现Read类型类,它以显然的方式工作:只要编译器知道你在使用read预期得到类型为RPS的值,read "Rock"就会变成Rock等。 在您的main函数中,您需要做的就是更改以下内容:
putStrLn (_shoot choice Rock)

为了

putStrLn (_shoot (read choice) Rock)

由于_shoot的类型签名告诉GHC它的第一个参数必须是一个RPS值,因此它将知道要使用为您的RPS类型定义的read实例,一切都应该很好,因为您已经限制了有效用户选择为那3个特定字符串之一。
(请注意,对于较大的程序,有更安全和更好的处理方式 - 参见Willem的答案中的一种简单方法 - 但这对于基本学习练习来说是可以的。)

1
谢谢,使用Maybe类型对我来说似乎更有意义,但知道有不同的方法做事也很好。 - RichardForrester

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接