Haskell: монадический захват?

У меня есть некоторые функции, написанные на C, которые я вызываю из Haskell. Эти функции возвращаютIO (CInt), Иногда я хочу запустить все функции независимо от того, что из них возвращает, и это легко. Для примера кода это общая идея того, что происходит в настоящее время:

Prelude> let f x = print x >> return x
Prelude> mapM_ f [0..5]
0
1
2
3
4
5
Prelude>

Я получаю желаемые побочные эффекты, и мне плевать на результаты. Но теперь мне нужно остановить выполнение сразу после первого элемента, который не возвращает желаемый результат. Допустим, возвращаемое значение 4 или выше требует остановки выполнения - то, что яwant сделать это:

Prelude> takeWhile (<4) $ mapM f [0..5]

Что дает мне эту ошибку:

<interactive>:1:22:
    Couldn't match expected type `[b]' against inferred type `IO a'
    In the first argument of `mapM', namely `f'
    In the second argument of `($)', namely `mapM f ([0 .. 5])'
    In the expression: takeWhile (< 4) $ mapM f ([0 .. 5])

И это имеет смысл для меня - результат все еще содержится в монаде ввода-вывода, и я не могу просто сравнить два значения, содержащиеся в монаде ввода-вывода. Я знаю, что именно в этом и заключается цель монад - объединение результатов в цепочку и отбрасывание операций при выполнении определенного условия - но есть ли простой способ «обернуть» монада IO в этом случае, чтобы прекратить выполнение цепочки при условии моего выбора, без написания экземпляраMonadPlus?

Могу ли я просто "снять" значения изf, для целей takeWhile?

Это решение для функторов? Функторы не "щелкнули" со мной еще, но у меня вроде есть впечатление, что это может быть хорошая ситуация, чтобы использовать их.

Update:

У @sth есть самый близкий ответ на то, что я хочу - на самом деле, это почти то же, что и я, но я все еще хотел бы увидеть, есть лиstandard решение, которое не является явно рекурсивным - это ведь Haskell! Оглядываясь назад на то, как я сформулировал свой вопрос, теперь я вижу, что не достаточно ясно о своем желаемом поведении.

f Функция, которую я использовал выше для примера, была просто примером. Реальные функции написаны на C и используются исключительно для их побочных эффектов. Я не могу использовать предложение Тома оmapM_ f (takeWhile (<4) [0..5]) потому что я понятия не имею, приведет ли какой-либо ввод к успеху или неудаче, пока не будет выполнен.

На самом деле меня не волнует возвращаемый список - я просто хочу вызывать функции C, пока список не будет исчерпан или первая функция C не вернет код ошибки.

В псевдокоде в стиле C мое поведение будет следующим:

do {
    result = function_with_side_effects(input_list[index++]);
} while (result == success && index < max_index);

Итак, еще раз, ответ @ sth выполняет точное поведение, которое я хочу, за исключением того, что результаты могут (должны?) Быть отброшены.dropWhileM_ Функция будет эквивалентна для моих целей. Почему нет такой функции илиtakeWhileM_ в Control.Monad? Я вижу, что там былоаналогичное обсуждение в списке рассылки, но, похоже, ничего не вышло.

 Jeremy List29 июн. 2017 г., 04:15
Это также немного разочаровывает, чтоsortBy не определяется с точки зренияsortByM :: Monad m => (a -> a -> m Ordering) -> [a] -> m [a].

Ответы на вопрос(5)

Edit: Теперь я вижу, что вы ищете.

Gbacon опубликовал хорошийsequenceWhile функция, которая является почти "примитивом" тебе нужно.

На самом деле, поскольку вас интересуют только побочные эффекты,sequenceWhile_ должно быть достаточно. Вот определение (снова, вдохновленный gbacon, проголосуйте за него!):

sequenceWhile_ :: (Monad m) => (a -> Bool) -> [m a] -> m ()
sequenceWhile_ p xs = foldr (\mx my -> mx >>= \x -> when (p x) my)
                            (return ()) xs

Вы называете это так:

Prelude Control.Monad> sequenceWhile (<4) $ map f [1..]

Original answer:

Вы не можете "просто отменить" значения изIO Монада для использования сtakeWile, но вы можете "поднять"takeWhile для использования в монаде!

liftM функция возьмет на себя функцию(a -> b) к функции(m a -> m b), гдеm это монада

(Как примечание, вы можете найти такую функцию, выполнив поиск по ее типу наHoogleв этом случае путем поиска:Monad m => (a -> b) -> (m a -> m b))

СliftM вы можете сделать это:

Prelude> :m + Control.Monad
Prelude Control.Monad> let f x = print x >> return x
Prelude Control.Monad> liftM (takeWhile (<4)) $ mapM f [0..5]
0
1
2
3
4
5
[0,1,2,3]

Теперь это может быть не то, что вы хотели.mapM будет применятьf Функция для всего списка в последовательности, прежде чем возвращать список. Этот результирующий список затем передается в поднятыйtakeWhile функция.

Если вы хотите прекратить печать после третьего элемента, вам придется прекратить вызывать печать. Это означает, что не применяетсяf к такому элементу. Таким образом, вы получите что-то простое, например:

Prelude> mapM_ f (takeWhile (<4) [0..5])

Кстати, если вам интересноwhy mapM сначала напечатает все, прежде чем вернуть список. Вы можете увидеть это, заменив функции их определениями:

mapM f [0..1]
=
sequence (map f [0..1])
=
sequence (f 0 : map f [1..1])
=
sequence (f 0 : f 1 : [])
=
sequence ((print 0 >> return 0) : f 1 : [])
= 
sequence ((print 0 >> return 0) : (print 1 >> return 1) : [])
=
do x  <- (print 0 >> return 0)
   xs <- (sequence ((print 1 >> return 1) : []))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y  <- (print 1 >> return 1)
             ys <- sequence ([])
             return (y:ys))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y  <- (print 1 >> return 1)
             ys <- return []
             return (y:ys))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y <- (print 1 >> return 1)
             return (y:[]))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (print 1 >> return (1:[]))
   return (x:xs)
=
do x <- (print 0 >> return 0)
   print 1
   return (x:1:[])
=
do print 0
   print 1
   return (0:1:[])

Этот процесс замены функций их определения называетсяequational reasoning.

Если я не допустил ошибок, теперь вы можете (надеюсь) увидеть, чтоmapM (с помощьюsequence) сначала печатает все, иthen возвращает список.

 Mark Rushakoff16 июл. 2009 г., 14:53
Это было не то реальное поведение, которое я хотел, но +1 для хорошего объяснения. LiftM теперь имеет больше смысла.

Вы можете использовать один из& Quot; Список & Quot; пакет.

import Control.Monad.ListT (ListT)
import Data.List.Class (execute, fromList, joinM, takeWhile)
import Prelude hiding (takeWhile)

f x = print x >> return x
main =
  execute . takeWhile (< 4) .
  joinM $ fmap f (fromList [0..5] :: ListT IO Int)
fromList [0..5] creates a monadic list containing 0..5 which performs no monadic actions fmap f to that list results in a ListT IO (IO Int) which still performs no monadic actions, just contains ones. joinM turns that into a ListT IO Int. every contained action would get executed when the item is consumed and its result will be the value in the list. takeWhile is generalized for any List. Both [] and "Monad m => ListT m" are instances of List. execute consumes the monadic list, executing all its actions. In case you are interested in the results you can use "toList :: List m => m a -> ItemM m [a]" ("ItemM (ListT IO)" is IO). so in this case it's "toList :: ListT IO a -> IO [a]". Better yet you can keep using higher-order functions such as scanl, etc to process the monadic list as it is being executed.
Решение Вопроса

Вы можете определитьпоследовательность как

sequence xs = foldr (liftM2 (:)) (return []) xs

Проблема сliftM2 что вы видели, что у вас нет возможности остановитьсяm2что может бытьlaunchTheMissiles!

liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c
liftM2 f m1 m2 = do
    x1 <- m1
    x2 <- m2
    return (f x1 x2)

С помощьюguard как в следующем кажется привлекательным:

sequenceUntil p xs = foldr (myLiftM2 p (:)) (return []) xs
  where myLiftM2 p f m1 m2 = do
            x1 <- m1
            guard $ p x1
            x2 <- m2
            return (f x1 x2)

Приведенный выше код потерпит неудачу в вашем приложении, потому что монада ввода-вывода не является экземпляромMonadPlus.

Так что держи его за руку еще немного

module Main where

import Control.Monad

printx :: Int -> IO Int
printx x = do
    print x
    return x

sequenceUntil :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
sequenceUntil p xs = foldr (myLiftM2 (:) []) (return []) xs
  where myLiftM2 f z m1 m2 = do
            x1 <- m1
            if p x1 then do x2 <- m2
                            return $ f x1 x2
                    else return z

main :: IO ()
main = do
  let as :: [IO Int]
      as = map printx [1..10]
  ys <- sequenceUntil (< 4) as
  print ys

Даже еслиas список действий от 1 до 10, вывод

1
2
3
4
[1,2,3]

Отбрасывание результатов тогда тривиально:

sequenceUntil_ :: (Monad m) => (a -> Bool) -> [m a] -> m ()
sequenceUntil_ p xs = sequenceUntil p xs >> return ()

main :: IO ()
main = do
  let as :: [IO Int]
      as = map printx [1..]
  sequenceUntil_ (< 4) as

Обратите внимание на использование[1..] что показывает новый комбинаторподдерживает лень.

Вы можете предпочестьspanM:

spanM :: (Monad m) => (a -> Bool) -> [m a] -> m ([a], [m a])
spanM _ [] = return ([], [])
spanM p (a:as) = do
  x <- a
  if p x then do (xs,bs) <- spanM p as
                 return (x:xs, bs)
         else return ([x], as)

Обратите внимание, что он немного отличается отпролет в том, что он включает в себя ошибочный элемент в списке результатов. Секунда пары - это оставшиеся действия. Например:

*Main> (xs,bs) <- spanM (< 4) as
1
2
3
4
*Main> xs  
[1,2,3,4]
*Main> sequence bs
5
6
7
8
9
10
[5,6,7,8,9,10]

Еще одна альтернатива:

untilM :: Monad m => (a -> Bool) -> [m a] -> m ()
untilM p (x:xs) = do
  y <- x
  unless (p y) $ untilM p xs

Обратите внимание, что смысл предиката дополняется:

*Main> untilM (>= 4) as
1
2
3
4
 17 июл. 2009 г., 03:23
Кто-то предложил лучшее имя: sequenceUntil. Я также добавил spanM в качестве альтернативы.
 16 июл. 2009 г., 19:15
+1, хорошее определениеsequenceWhile, Я сначала пытался определить это с помощьюfoldM вместоfoldr, но это, очевидно, не сработает, так как это все еще заставляет весь список.

Совсем недавно вы можете использоватьMonadList взлом, который включает в себяудобные функции как takeWhileM, dropWhileM, deleteByM и многие другие.

Я не думаю, что есть что-то вродеtakeWhileM в стандартной библиотеке, но вы могли бы написать это самостоятельно, чтобы выполнялось только столько операций ввода-вывода, сколько необходимо:

takeWhileM :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
takeWhileM _ [] = return []
takeWhileM p (a:as) =
   do v <- a
      if p v
         then do vs <- takeWhileM p as
                 return (v:vs)
         else return []

Предоставленный список оценивается только до тех пор, пока не будет найден элемент, который не соответствует предикату:

*Main> takeWhileM (<4) (map f [1..5])
1
2
3
4
[1,2,3]
 16 июл. 2009 г., 01:43
@sth, @ephemient: не в стандартной библиотеке, а в моем недавно выпущенном & quot; генераторе & quot; package (in hackage) есть более общая функция takeWhile. вашему takeWhileM нужен список действий, чтобы он не зависел от действий внутри монады, в то время как генератор takeWhile получает список с монадой m, допускающий то же и другое.
 15 июл. 2009 г., 23:59
Для согласованности сControl.Monad.filterMЯ ожидаю, что тип больше похож наtakeWhileM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]; это сделало бы использование что-то вродеjoin . liftM sequence . takeWhileM (liftM (< 4)), Но это немного уродливее для того, что хочет ОП, так что.
 Mark Rushakoff16 июл. 2009 г., 14:56
+1 за точное соответствие поведения, которое я хотел. Я обновил вопрос и собираюсь подождать и посмотреть, есть ли у кого-нибудь стандартное решение для lib. Если его нет, я принимаю ваш ответ.

Ваш ответ на вопрос