【问题标题】:Haskell: how do you check runtime types on IO?Haskell:你如何检查 IO 上的运行时类型?
【发布时间】:2019-06-09 10:13:29
【问题描述】:

我正在阅读一些有关 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."

【问题讨论】:

  • 这是“布尔盲”的常见实例,即即使您手头有更多信息,也将您知道的信息减少为布尔值。 isRPS 返回一个布尔值(携带真或假)而不是更多信息类型(携带 RPS 值或错误)。在这种情况下,Maybe RPS 就足够了。有时你想要Either SomeError RPS,如果你有多个错误要返回。 Bool 在 Haskell 中不像在其他一些语言中那样频繁使用。

标签: haskell io typechecking


【解决方案1】:

您没有将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 readRPS (map toLower choice) of
        Just rps -> putStrLn (_shoot rps Rock) 
        Nothing -> putStrLn "Invalid choice."
    main

【讨论】:

  • 谢谢,Maybe 的完美用例——我应该看到的。我刚刚注意到需要切换 rps 和 Rock 才能使游戏正常运行。
【解决方案2】:

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

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

data RPS = Rock | Paper | Scissors deriving Read

deriving Read 所做的是给RPS 一个Read 类型类的默认实例,它的工作方式很明显:read "Rock" 将变为Rock 等等,前提是编译器知道你正在使用read 在需要 RPS 类型值的上下文中。

那么您需要做的就是在 main 函数中进行更改:

putStrLn (_shoot choice Rock)

putStrLn (_shoot (read choice) Rock)

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

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

【讨论】:

  • 谢谢,使用 Maybe 类型对我来说似乎更有意义,但很高兴知道有不同的方式来做事。
猜你喜欢
  • 2023-04-03
  • 1970-01-01
  • 2013-11-02
  • 1970-01-01
  • 2020-06-20
  • 1970-01-01
  • 2014-06-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多