我不明白为什么以下代码的行为方式如下:
myand :: Bool -> Bool -> Bool
myand True True = True
myand _ _ = False
containsAandB :: String -> IO Bool
containsAandB s = do
containsA <- do
putStrLn $ "Check if 'a' is in " ++ s
return $ 'a' `elem` s
containsB <- do
putStrLn $ "Check if 'b' is in " ++ s
return $ 'b' `elem` s
return $ containsA `myand` containsB
这是我测试函数时的输出:
*Main> containsAandB "def"
Check if 'a' is in def
Check if 'b' in in def
False
请注意 (&&) 的行为就像 'myand',我只是编写了一个自定义函数来更好地可视化正在发生的事情。
我对
'Check if 'b' is in def
感到惊讶部分自 containsA
已经为假,因此无论 containsB
如何都可以评估“myand” .问题一:
containsB
有什么特别的原因吗?也需要评估?我的理解是 containsB <- do ...
除非需要,否则不会评估语句,但我的猜测是,由于它是 IO,因此它的行为可能会有所不同,因此没有副作用?问题2:
在不处理嵌套
containsA
的情况下,获得所需行为的最佳实践方法是什么(如果 containsB
为假,if-else
未选中)条款?
最佳答案
Question 1: Is there a particular reason why containsB has to be evaluated too? My understanding was that the containsB <- do ... statement is not evaluated unless it's required but my guess is that this might behave differently since it's IO and therefore not free of side effects?
你的实验有缺陷,因为你执行了
IO
. IO
的重要方面之一是IO
的顺序吗?声明受到尊重。所以即使由于懒惰,我们也不需要某个值,IO
部分被执行。这在
IO
的世界中是合乎逻辑的。 : 假设我们读取一个文件,我们有三个部分。我们读了前两部分,然后我们读了第三部分。但是现在想象一下,由于懒惰,第二个 IO
命令永远不会执行。那么这意味着第三部分实际上读取了文件的第二部分。简而言之由于
IO
,语句被评估 .但只有 IO
陈述。所以包裹在 return
中的值不评估,除非你需要它。支票'b' `elem` s
只有在我们需要的时候才会发生。然而,有一些方法可以“欺骗”
IO
出此。例如 trace
(来自 Debug.Trace
)模块将执行“不安全的 IO”:如果它被评估,它将打印错误消息。如果我们写:Prelude> import Debug.Trace
Prelude Debug.Trace> myand (trace "a" False) (trace "b" False)
我们有:
Prelude Debug.Trace> myand (trace "a" False) (trace "b" False)
a
False
Question2: What's the best practice approach to get the desired behavior (if containsA is false, containsB is not checked) without dealing with nested if-else clauses?
如前所述,正常行为是
containsB
不评估。但是如果你执行 IO
操作,必须在您实际进行检查之前执行这些操作。这基本上是 (>>=)
的方面之一。 IO
的运算符(您在 do
block 中隐含地使用此运算符)句柄。
关于haskell - 关于惰性的 'and' 子句评估,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50783116/