Haskell: монадический takeWhile?
у меня есть некоторые функции, написанные на 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 или выше требует остановки выполнения-тогда что I хочу сделать это:
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, и я не могу просто сравнить два значения, содержащиеся в монаде IO. Я знаю, что именно в этом и заключается цель монад-сцепление результатов и отбрасывание операций при выполнении определенного условия, - но существует ли простой способ "завернуть" монаду IO в этом случае, чтобы прекратить выполнение цепочки при условии моего выбора, не написав экземпляр MonadPlus
?
Я могу просто "unlift" значения f
, для целей takeWhile?
это решение, в котором подходят функторы? Функторы еще не" щелкнули " со мной, но у меня сложилось впечатление, что это может быть хорошая ситуация для использования их.
обновление:
@sth имеет самый близкий ответ на то, что я хочу - на самом деле, это почти именно то, что я собирался, но я все равно хотел бы посмотреть, есть ли стандартный решение, которое не является явно рекурсивным - это Haskell, в конце концов! Оглядываясь назад на то, как я сформулировал свой вопрос, теперь я вижу, что не был достаточно ясен о своем желаемом поведении.
на f
функция, которую я использовал выше для пример был просто примером. Реальные функции написаны на языке C и используются исключительно для их побочных эффектов. Я не могу использовать предложение @Tom 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_
в Управления.Монада? Я вижу, что был аналогичное обсуждение в списке рассылки
5 ответов
вы можете определить последовательность as
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)
обратите внимание, что он немного отличается от span в том, что он включает элемент failing в списке результатов. Вторая пара-это оставшиеся действия. Например:
*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
Я не думаю, что есть что-то вроде takeWhileM
в стандартной библиотеке, но вы можете написать ее сами, чтобы было выполнено только столько IO, сколько необходимо:
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]
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..]
оригинальный ответ:
вы не можете просто "unlift" значения из 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]
теперь, это может быть не то, что вы хотели. The mapM
применить f
функция для всего списка в последовательности, прежде чем возвращать список. Этот результирующий список затем передается в lifted
вы можете использовать один из "список" пакета.
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]
создает монадический список, содержащий 0..5 который не выполняет монадических действий -
fmap f
к этому списку приводитListT IO (IO Int)
который по-прежнему не выполняет монадических действий, просто содержит их. -
joinM
превращается вListT IO Int
. каждое содержащееся действие будет выполняться, когда элемент потребляется, и его результатом будет значение в список. -
takeWhile
обобщается для любогоList
. Оба!--8--> и "Monad m => ListT m
" являются экземплярамиList
. -
execute
потребляет монадический список, выполняя все его действия. - в случае, если вы заинтересованы в результатах, которые вы можете использовать
"toList :: List m => m a -> ItemM m [a]"
("ItemM (ListT IO)
" составляетIO
). так что в данном случае это "toList :: ListT IO a -> IO [a]
". Еще лучше вы можете продолжать использовать функции более высокого порядка, такие какscanl
и т. д. Для обработки монадического списка по мере его выполнения.
в последнее время, вы можете использовать MonadList hackage, который включает в себя удобные функции как takeWhileM, dropWhileM, deleteByM и многое другое.