Оценка пункта «и» в отношении лени - PullRequest
0 голосов
/ 10 июня 2018

Я не понимаю, почему следующий код ведет себя так, как он:

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 уже ложно, поэтому 'myand' может оцениваться независимо от containsB.

Вопрос 1:
Есть ликакая конкретная причина, почему containsB тоже должна оцениваться?Насколько я понимаю, оператор containsB <- do ... не оценивается, если только он не требуется, но я предполагаю, что он может вести себя по-другому, так как это ввод-вывод и, следовательно, он не свободен от побочных эффектов?

Вопрос2:
Каков наилучший практический подход для получения желаемого поведения (если containsA ложно, containsB не проверено) без работы с вложенными предложениями if-else?

Ответы [ 2 ]

0 голосов
/ 10 июня 2018
Блоки

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 ... первым аргументом вызова >>=>>=>>) для IO определены, поэтому он всегда запускает свой первый аргумент.Таким образом, чтобы перейти к последнему return $ ..., оба вызова putStrLn уже должны быть выполнены.

Это поведение не ограничено монадой ввода-вывода;например, см. Разница между ленивой и строгой монадой Хаскелла (или преобразователями) .

Каков наилучший практический подход для получения желаемого поведения (если containsA равно false, hassB не проверяется)не имея дело с вложенными пунктами if-else?

С ними можно разобраться раз и навсегда:

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 находится в http://hackage.haskell.org/package/extra-1.6.8/docs/Control-Monad-Extra.html как (&&^) вместе с другими аналогичными функциями).

0 голосов
/ 10 июня 2018

Вопрос 1: Есть ли какая-то особая причина, по которой также должен быть проверен параметр «В»?Насколько я понимаю, оператор containsB <- do ... не оценивается, если только он не требуется, но я предполагаю, что он может вести себя по-другому, так как это ввод-вывод и, следовательно, он не свободен от побочных эффектов? </p>

Ваш экспериментошибочен, потому что вы выполняете IO.Одним из важных аспектов IO является соблюдение порядка IO операторов.Поэтому, даже если из-за лени нам не нужно определенное значение, часть IO выполняется.

Это логично в мире IO: представьте, что мы читаем файл, и у нас естьтри части.Мы читаем первые две части, а затем читаем третью.Но теперь представьте, что из-за лени вторая IO команда никогда не выполняется.Тогда это будет означать, что третья часть фактически читает вторую часть файла.

Короче говоря из-за IO, операторы оцениваются .Но только IO заявления.Таким образом, значение, заключенное в return, оценивается , а не , если вам это не нужно.Проверка 'b' `elem` s происходит только тогда, когда она нам нужна.

Однако существуют способы « обмануть » из IO из этого.Например, trace (из Debug.Trace) модуль выполнит « небезопасный ввод-вывод »: он напечатает сообщение об ошибке, если его вычислить.Если мы напишем:

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: Каков наилучший практический подход для получения желаемого поведения (если содержитfalse, containsB не проверяется), не имея дело с вложенными предложениями if-else?

Как уже было сказано, нормальным поведением является то, что containsB оценивается , а не .Но если вы выполняете действия IO, эти должны быть выполнены до того, как вы действительно выполните проверку.Это, в сущности, один из аспектов, который обрабатывает оператор (>>=) для IO (вы используете этот оператор неявно в блоке do).

...