У меня есть функция f :: [a] -> b
, которая работает с бесконечными списками (например, take 5
, takeWhile (< 100) . scanl (+) 0
и т. д.). Я хочу передать этой функции значения, сгенерированные строгими монадическими действиями (например, randomIO
).
Из этот вопрос, я узнал, что подход с трюками repeat
и sequence
не работает для строгих монад, как показано в примере ниже:
import Control.Monad.Identity
take 5 <$> sequence (repeat $ return 1) :: Identity [Int]
-- returns `Identity [1,1,1,1,1]`
-- works because Identity is non-strict
take 5 <$> sequence (repeat $ return 1) :: IO [Int]
-- returns `*** Exception: stack overflow`
-- does not work because IO is strict
Поэтому вместо этого я подумал об использовании функции «внутри» монадического контекста. Меня вдохновил этот пример циклического программирования и пробовал:
let loop = do
x <- return 1
(_, xs) <- loop
return (take 5 xs, x:xs)
in fst loop :: Identity [Int]
-- Overflows the stack
а также
import Control.Monad.Fix
fst <$> mfix (\(_, xs) -> do
x <- return 1
return (take 5 xs, x:xs)) :: Identity [Int]
-- Overflows the stack
и даже
{-# LANGUAGE RecursiveDo #-}
import System.Random
loop' = mdo
(xs', xs) <- loop xs
return xs'
where loop xs = do
x <- randomIO
return (take 5 xs, x:xs)
print $ loop'
-- Returns a list of 5 identical values
Но ничего из этого не работает. Я также попробовал подход Conduit
, который не сработал даже в случае Identity
:
import Conduit
runConduitPure $ yieldMany [1..] .| sinkList >>= return . take 5
Поэтому я хотел бы знать:
Почему ни один из описанных выше «круговых» подходов не работает?
Если существует решение этой проблемы, не связанное с
unsafeInterleaveIO
. (может бытьiteratee
с,Arrow
с?)
randoms <$> newStdGen :: Random a => IO [a]
— это бесконечный случайный список всего, что вы хотите (то естьRandom
). - person Alec   schedule 11.02.2017randomIO
только для простоты примеров. На практике я хотел бы обрабатывать сообщения, полученные через сокеты. - person Douglas Vieira   schedule 11.02.2017randomIO
я используюreceive sock
, гдеsock
— это монада ZeroMQSocket z
(см. hackage.haskell.org/package/zeromq4-haskell-0.6.5/docs/ ) - person Douglas Vieira   schedule 11.02.2017conduit
, разве вы не должны использоватьtake
внутри трубопровода?>>> runConduit $ sourceRandom .| takeC 5 .| sinkList :: IO [Bool]
дает мне[False,False,False,False,True]
ЗдесьsourceRandom
заменяет ваш поток сообщений, но чистый случай тоже работает. На самом деле список не должен быть составлен, вы должны делать с ним все, что собирались делать внутри канала. - person Michael   schedule 12.02.2017Maybe
ленив, ноtake 5 <$> sequence (repeat $ Just 1) :: Maybe [Int]
тоже зависает, так как если бы миллионный элемент списка былNothing
, он вернул быNothing
. Точно так жеtake 5 <$> sequence (repeat [1]) :: [[Int]]
будет[]
, если где-то в списке списков есть пустой список. - person Michael   schedule 12.02.2017Maybe
, я думаю, проблема в том, что единственная возможная фиксированная точка в монадическом контексте (междуJust a
илиNothing
) — этоNothing
, поэтомуsequence
инг бесконечного спискаJust a
просто зависнет навсегда.Identity a
не имеет этой проблемы, потому что фиксированная точка монадического контекста ужеIdentity a
(то же самое интуитивно применимо и дляIO a
, если бы не было побочных эффектов). - person Douglas Vieira   schedule 12.02.2017sequence
, т.е.foldr (\m ms -> (:) <$> m <*> ms) (pure [])
вместе с монадной (аппликативной) реализацией в каждом конкретном случае, чтобы увидеть, почему вIdentity
дела идут так гладко, а вIO
(так часто) плохо. По сути, чтобы избежатьsequence
, у нас есть потоковые библиотеки. С точки зрения потоковых библиотекsequence
mapM
replicateM
и т. д. для списков ввода-вывода + являются фундаментальными красными флажками. - person Michael   schedule 12.02.2017seq (return undefined :: Identity ()) ()
,seq (return undefined :: Maybe ()) ()
,seq (return undefined :: IO ()) ()
. Как вы указали, случай сIdentity
выдает ошибку, так как он определяется как новый тип. Интересно, что случай сIO
не выдает ошибки, хотяprint 1 >> return ()
печатает1
в терминале. В ответе Джона Л. он упоминает строгость(>>=)
, которая здесь играет роль. - person Douglas Vieira   schedule 12.02.2017Control.Monad.Trans.State.Lazy
, если я пишуa = take 5 <$> sequence (repeat $ return 1) :: StateT () Identity [Int]
, я могу написать>>> evalStateT a ()
и получаюIdentity [1,1,1,1,1]
с>>> runStateT a ()
я получаюIdentity ([1,1,1,1,1],
, и он делает паузу, пока пытается получить () в конце радуги - поэтому происходит переполнение стека. СControl.Monad.Trans.State.Strict
оба заканчиваются переполнением стека.sequence
действительно довольно токсичен для списков, хотя, конечно, его можно использовать. - person Michael   schedule 12.02.2017