Как "продолжить" в цикле "Monad"?
Часто мне приходилось пропускать оставшуюся часть итерации (например, continue
в C) в Haskell:
forM_ [1..100] $ \ i ->
a <- doSomeIO
when (not $ isValid1 a) <skip_rest_of_the_iteration>
b <- doSomeOtherIO a
when (not $ isValid2 b) <skip_rest_of_the_iteration>
...
Однако мне не удалось найти простой способ сделать это. Единственный способ, о котором я знаю, вероятно, это Trans.Maybe
, но нужно ли использовать преобразование монады для достижения чего-то такого тривиального?
Ответы
Ответ 1
Помните, что подобные петли в Haskell не волшебны... они просто обычные первоклассные вещи, которые вы можете написать себе.
Для чего это стоит, я не думаю, что слишком полезно думать о MaybeT
как трансформатор Monad. Для меня MaybeT
- это просто оболочка newtype, чтобы дать альтернативную реализацию (>>=)
... так же, как вы используете Product
, Sum
, First
, And
и т.д., Чтобы дать альтернативные реализации mappend
и mempty
.
Прямо сейчас (>>=)
для вас - IO a -> (a -> IO b) -> IO b
. Но было бы более полезно иметь (>>=)
здесь IO (Maybe a) -> (a -> IO (Maybe b) -> IO (Maybe b)
. Как только вы перейдете к первому действию, которое возвращает Nothing
, это действительно невозможно "привязать" дальше. Именно это дает вам MaybeT
. Вы также получаете "пользовательский экземпляр" guard
, guard :: Bool -> IO (Maybe a)
вместо guard :: IO a
.
forM_ [1..100] $ \i -> runMaybeT $ do
a <- lift doSomeIO
guard (isValid1 a)
b <- lift $ doSomeOtherIO a
guard (isValid2 b)
...
и что он:)
MaybeT
тоже не волшебство, и вы можете добиться в основном того же эффекта, используя вложенный when
s. Это не нужно, это просто делает вещи намного проще и чище:)
Ответ 2
Вот как вы это сделаете, используя рекурсию bare-bones:
loop [] = return () -- done with the loop
loop (x:xs) =
do a <- doSomeIO
if ...a...
then return () -- exit the loop
else do -- continuing with the loop
b <- doSomeMoreIO
if ...b...
then return () -- exit the loop
else do -- continuing with the loop
...
loop xs -- perform the next iteration
а затем вызовите его с помощью:
loop [1..100]
Вы можете немного убрать это с помощью функции when
из Control.Monad:
loop [] = return ()
loop (x:xs) =
do a <- doSomeIO
when (not ...a...) $ do
b <- doSomeMoreIO
when (not ...b...) $ do
...
loop xs
В Control.Monad также есть unless
.
Используя @Ørjan Johansen полезный совет, вот простой пример:
import Control.Monad
loop [] = return ()
loop (x:xs) = do
putStrLn $ "x = " ++ show x
a <- getLine
when (a /= "stop") $ do
b <- getLine
when (b /= "stop") $ do
print $ "iteration: " ++ show x ++ ": a = " ++ a ++ " b = " ++ b
loop xs
main = loop [1..3]
Ответ 3
Если вы хотите перебрать список или другой контейнер для выполнения действий и/или создать итоговое значение, и вы найдете обычные удобные инструменты, такие как for_
и foldM
, недостаточно хороши для работы, вы можете рассмотреть foldr
, который достаточно силен для работы. Когда вы на самом деле не зацикливаете контейнер, вы можете использовать обычную старую рекурсию или потянуть что-то вроде https://hackage.haskell.org/package/loops
или (для совершенно другого вкуса) https://hackage.haskell.org/package/machines
или, возможно, https://hackage.haskell.org/package/pipes
.