评估'和'关于懒惰的条款

时间:2018-06-10 11:17:21

标签: haskell lazy-evaluation

我不明白为什么以下代码的行为方式如下:

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)?

2 个答案:

答案 0 :(得分:7)

  

问题1:   是否还有一个特殊原因需要对containsB进行评估?我的理解是,除非它是必需的,否则不会评估containsB&lt; - do ...语句,但我的猜测是,这可能表现不同,因为它是IO,因此没有副作用?

您的实验存在缺陷,因为您执行IOIO的一个重要方面是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一样位于(&&^),以及其他类似的功能。)