【问题标题】:How to check if an IO () is equivalent to another IO ()?如何检查一个 IO() 是否等价于另一个 IO()?
【发布时间】:2019-07-27 21:46:30
【问题描述】:

我正在使用hspec 进行一些基本测试。

我有一个argsParser 函数,它给出了一些参数,返回或者打印它们的有效性。

argsParser :: [String] -> IO ()
argsParser args | null args = print "no args provided"
                | not $ null args && length args < 2 = print "no file name provided"
                | length args > 2 == print "too many arguments"
                | otherwise = goAhead args

问题是我不确定如何将IO () 与另一个IO () 进行比较。

我认为liftIO 可能会有所帮助

x <- liftIO $ print "something"
y <- liftIO $ print "anything"

我明白了

x == y = True

我怀疑是因为两者都是actions

【问题讨论】:

  • x==yTrue,因为 xy 都是 ()。它们不是动作 - 如果您定义 x = ... 而不是 x &lt;- ...,就会出现这种情况,在这种情况下您会收到错误,因为无法检查类型 IO () 是否相等。
  • 您可以使用免费的 monad 来获得平等性和可测试性,但它们是有代价的:请参阅 stackoverflow.com/questions/13352205/what-are-free-monads(您可以在此处阅读有关警告和替代方案:markkarpov.com/post/free-monad-considered-harmful.html
  • argsParser 做的事情太多了。 argsParser :: [String] -&gt; Either ArgsError [String] 之类的东西要么返回有效字符串,要么返回其他函数可以处理的错误 (data ArgsError = NoArgs | NoFilename | TooManyArgs)。

标签: haskell


【解决方案1】:

您无法将 IO 操作与另一个操作进行比较。可计算性理论指出,没有办法确定两个 IO 值是否相等。因此,Haskell 中没有实例 Eq (IO a)

您最多可以尝试运行这两个动作,从外部观察它们的效果并比较它们的效果——这并不总是有效(例如,如果一个动作是一个无限循环,如果该动作需要用户输入)但它可能足够接近。可以通过将操作作为子进程运行、重定向其标准输出/错误来实现此检查。

(但为什么要比较 IO 操作?这很不寻常)

【讨论】:

  • 好吧,如果您可以比较 IO 操作,那将非常有用。你可以从print True == print foundFermatCounterExample开始。
  • @PaulJohnson 这听起来对我有误导性。我可以比较布尔值,但 True == foundFermatCounterExample 究竟在哪里得到我?如果你传递的参数可能在产生它们的值之前永远循环,这不是(==) 的错。
  • @DanielWagner 确定两个任意的IO () 值是否在所有情况下都做同样的事情相当于解决了停机问题。如果您可以解决停止问题,那么您可以通过测试反例搜索是否会停止来证明费马大定理。您实际上并没有运行搜索,您只需将其提交到停止测试。
  • @PaulJohnson 我的观点是确定两个任意的Bool 术语是否在所有情况下都做同样的事情等同于解决停机问题。因为这不是BoolIO () 之间的区别,所以不能解释我们有instance Eq Bool 而不是instance Eq (IO ()) 的区别。
  • @DanielWagner 我的猜测是Bool 的底部很无趣(error "..", undefined, fix id, ...)所以我们喜欢假装它们并不真正存在,因此我们接受instance Eq Bool。事实上,如果我们不这样做,任何类型都不会存在有意义的Eq 实例。相反,IO () 底部可能非常有趣,例如 forever (print 42)。此外,即使我们忽略底部,为了比较 x,y :: IO a(假设 Eq a),我们也需要实际运行 IO 操作,但这只会产生 IO Bool,并且使用不安全的操作来获得 Bool 会是一个非常糟糕的举动。
【解决方案2】:

首先,将argsParser 简化为检查数字的东西 参数;暂时不要做任何 IO。

import System.IO

data ArgsError = NoArgs | NoFilename | TooManyArgs
instance Show ArgsError where
    show NoArgs = "no args specified"
    show NoFilename = "no file name specified"
    show TooManyArgs = "too many arguments"

validateArgs :: [String] -> Either ArgsError [String]
validateArgs args | null args = Left NoArgs
                  | length args < 2 = Left NoFilename
                  | length args > 2 = Left TooManyArgs
                  | otherwise = Right args

goAhead :: [String] -> IO ()
-- as before

现在您只需要一个用于Either ArgsError [String] 值的处理程序,它是either :: (a -&gt; c) -&gt; (b -&gt; c) -&gt; Either a b -&gt; c 的简单应用程序。

main = do
    args <- getArgs
    either (hPrint stderr) goAhead (validatedArgs args)

您现在可以轻松测试validateArgs,并且可以说either (hPrint stderr) goAhead 不需要进行测试。

【讨论】:

    猜你喜欢
    • 2019-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-28
    • 1970-01-01
    • 2011-04-19
    • 1970-01-01
    • 2011-05-29
    相关资源
    最近更新 更多