我不明白为什么以下代码的行为方式如下:
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
请注意,(&amp;&amp;)的行为就像&#39; myand&#39;,我只是编写了一个自定义函数,以便更好地直观了解发生的事情。
我对'Check if 'b' is in def
部分感到惊讶,因为containsA
已经是假的,所以&#39; myand&#39;无论containsB
如何,都可以进行评估。
问题1:
是否还有一个特殊原因要求containsB
进行评估?我的理解是,containsB <- do ...
语句除非需要,否则不会被评估,但我的猜测是,由于它的IO可能表现不同,因此不会产生副作用?
问题2:
在不处理嵌套containsA
子句的情况下,获取所需行为的最佳实践方法是什么(如果containsB
为false,则不检查if-else
)?
答案 0 :(得分:7)
问题1: 是否还有一个特殊原因需要对containsB进行评估?我的理解是,除非它是必需的,否则不会评估containsB&lt; - do ...语句,但我的猜测是,这可能表现不同,因为它是IO,因此没有副作用?
您的实验存在缺陷,因为您执行IO
。 IO
的一个重要方面是IO
语句的顺序得到尊重。因此,即使由于懒惰,我们不需要某个值,IO
部分也会被执行。
这在IO
的世界中是合乎逻辑的:假设我们读了一个文件,我们有三个部分。我们阅读前两部分,然后我们阅读第三部分。但现在想象一下,由于懒惰,第二个IO
命令永远不会被执行。那意味着第三部分实际上会读取文件的第二部分。
因此,简而言之由于IO
,语句已评估。但只有IO
语句。因此,除非您需要,否则return
中包含的值不进行评估。支票'b' `elem` s
仅在我们需要时才会发生。
但是有一些方法可以“欺骗”IO
。例如,trace
(来自Debug.Trace
)模块将执行“ unsafe 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
<强>问题2:强> 在不处理嵌套的if-else子句的情况下,获取所需行为的最佳实践方法是什么(如果containsA为false,则不检查containsB)?
如前所述,正常行为是containsB
未评估。但是,如果您执行IO
个操作,那么会在您实际执行检查之前执行。这基本上是(>>=)
的{{1}}运算符(您在IO
块中使用此运算符)处理的方面之一。
答案 1 :(得分:3)
do
块被转换为对>>=
和>>
的调用。特别是,你的代码变成了(除非我错过了一些括号)
containsAandB s =
(putStrLn $ "Check if 'a' is in " ++ s >>
return $ 'a' `elem` s) >>= (\containsA ->
(putStrLn $ "Check if 'b' is in " ++ s >>
return $ 'b' `elem` s) >>= (\containsB ->
return $ containsA `myand` containsB))
所以containsB <- do ...
实际上不是语句;它使do ...
部分成为>>=
调用的第一个参数。 >>=
的{{1}}(和>>
)已定义,因此它始终运行其第一个参数。所以要到达最后IO
,两个return $ ...
调用必须已经运行。
此行为不仅限于IO monad;例如见Difference between Haskell's Lazy and Strict monads (or transformers)。
获取所需行为的最佳实践方法是什么(如果containsA为false,则不检查containsB)而不处理嵌套的if-else子句?
你可以一劳永逸地处理它们:
putStrLn
或
andM :: (Monad m) => m Boolean -> m Boolean -> m Boolean
andM m1 m2 = do
x <- m1
case x of
True -> m2
False -> return False
containsAandB s = andM
(do
putStrLn $ "Check if 'a' is in " ++ s
return $ 'a' `elem` s)
(do
putStrLn $ "Check if 'b' is in " ++ s
return $ 'b' `elem` s)
(containsAandB :: String -> IO Bool
containsAandB s = do
let containsA = do
putStrLn $ "Check if 'a' is in " ++ s
return $ 'a' `elem` s
let containsB = do
putStrLn $ "Check if 'b' is in " ++ s
return $ 'b' `elem` s
containsA `andM` containsB
与andM
一样位于(&&^)
,以及其他类似的功能。)