Haskell: monadic 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 или выше требует исполнения для остановки - тогда я хочу сделать следующее:
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
?
Могу ли я просто "разблокировать" значения из f
, для целей takeWhile?
Является ли это решением, когда функторы подходят? Функторы еще не "нажали" со мной, но у меня вроде бы создается впечатление, что это может быть хорошей ситуацией для их использования.
<ч/" > Update:
@sth имеет самый близкий ответ на то, что я хочу - на самом деле, это почти то, что я собирался, но мне все же хотелось бы посмотреть, есть ли стандартное решение, которое явно не рекурсивно - это Хаскелл, в конце концов! Оглядываясь назад, как я сформулировал свой вопрос, теперь я вижу, что я недостаточно ясно о желаемом поведении.
Функция 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 answer выполняет точное поведение, которое я хочу, за исключением того, что результаты могут (должны?) быть отброшены. Функция A dropWhileM_
была бы эквивалентна для моих целей. Почему в Control.Monad нет такой функции или takeWhileM_
? Я вижу, что было аналогичное обсуждение в списке рассылки, но, похоже, ничего из этого не произошло.
Ответы
Ответ 1
Вы можете определить sequence как
sequence xs = foldr (liftM2 (:)) (return []) xs
Проблема с liftM2
, которую вы видели, у вас нет возможности остановить m2
, что может be 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)
Приведенный выше код не будет работать в вашем приложении, потому что монада IO не является экземпляром 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 тем, что включает в себя элемент сбой в списке результатов. Вторая пара - это остальные действия. Например:
*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
Ответ 2
Я не думаю, что в стандартной библиотеке есть что-то вроде 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]
Ответ 3
Изменить: Теперь я вижу, что вы ищете.
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..]
Оригинальный ответ:
Вы не можете просто "разблокировать" значения из IO
Monad для использования с takeWile
, но вы можете "поднять" takeWhile
для использования в Monad!
Функция 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])
Кстати, стоит ли задаваться вопросом, почему 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:[])
Этот процесс замены функций их определениями называется эквациональным рассуждением.
Если я не ошибался, вы можете (надеюсь) увидеть, что mapM
(используя sequence
) сначала печатает все, а затем возвращает список.
Ответ 4
Вы можете использовать тот из пакета "List" .
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
. Оба []
и "Monad m => ListT m
" являются экземплярами List
.
-
execute
потребляет монадический список, выполняя все его действия.
- Если вас интересуют результаты, вы можете использовать
"toList :: List m => m a -> ItemM m [a]"
( "ItemM (ListT IO)
" is IO
). поэтому в этом случае это "toList :: ListT IO a -> IO [a]
". Еще лучше вы можете использовать функции более высокого порядка, такие как scanl
и т.д. Для обработки монадического списка по мере его выполнения.
Ответ 5
Совсем недавно вы можете использовать MonadList хакеры, которые включают удобные функции, такие как takeWhileM, dropWhileM, deleteByM и многие другие.