Haskell: 你如何检查 IO 上的运行时类型?

Haskell: how do you check runtime types on IO?

我正在 Haskell 上完成一些介绍性的 material 并尝试完成这个愚蠢的石头剪刀布的命令行实现。

我认为输入的类型保护足以让编译器相信输入是 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."

您没有将 choice(即 String)转换为 RPS,甚至更好的 Maybe RPS:

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

这里我们因此 return 一个 Just x 给定输入是有效的(x 相应的 RPS 项),或者 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

您就快完成了,您只需要 read 函数将用户的字符串转换为您的 RPS 数据类型。

您需要做的第一件事是使 RPS 成为 Read 类型类的一个实例。这可以通过将您的 data 声明修改为:

来轻松完成
data RPS = Rock | Paper | Scissors deriving Read

deriving Read 所做的是给 RPS 一个 Read 类型类的默认实例,它以显而易见的方式工作:read "Rock" 将变成 Rock依此类推,前提是编译器知道您在需要 RPS 类型值的上下文中使用 read

然后您需要做的就是在您的 main 函数中更改此内容:

putStrLn (_shoot choice Rock)

putStrLn (_shoot (read choice) Rock)

因为 _shoot 有一个类型签名告诉 GHC 它的第一个参数必须是一个 RPS 值,它会知道使用为你的 [=13] 定义的 read 的实例=] 类型,一切都应该很好,因为您已经将有效的用户选择限制为这 3 个特定字符串。

(请注意,对于较大的程序,有更安全和更好的方法来处理此类事情 - 请参阅 Willem 对一种简单方法的回答 - 但这对于基本学习练习来说很好。)