2017-02-11 12 views
3

У меня есть функция f :: [a] -> b, которая работает с бесконечными списками (например, take 5, takeWhile (< 100) . scanl (+) 0 и т. Д.). Я хочу передать эту функцию значениям, генерируемым строгими монадическими действиями (например, randomIO).Работа бесконечных списков со строгими монадами

С this question, я узнал, что трюк подход 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 

Таким образом, вместо этого, я думал об использовании функции «внутри «монадический контекст. Я был вдохновлен этой circular programming example и попробовал:

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 

Поэтому я хотел бы знать:

  1. Почему никто из «круговой» не подходит выше работы?

  2. Если существует решение, которое не включает unsafeInterleaveIO. (возможно iteratee s, Arrow ы?)

+3

В общем, это сложная проблема. Для случайных чисел, в частности, есть простой выход: 'randoms <$> newStdGen :: Random a => IO [a]' - это бесконечный случайный список того, что вы хотите (это «Random»). – Alec

+0

@Alec Спасибо за комментарий. Я использую 'randomIO' здесь только для простоты примеров. На практике я хотел бы обрабатывать сообщения, полученные через сокеты. –

+0

Так что-то вроде бесконечного списка сообщений, полученных от сокета? – Alec

ответ

4

Я использую randomIO здесь только для простоты примеров. На практике, я хотел бы обрабатывать сообщения, полученные с помощью сокетов

Это невозможно без unsafeInterleaveIO. Проблема в конце дня заключается в том, что действия IO должны быть упорядочены. Хотя порядок оценки ссылочно прозрачных значений не имеет значения, действия IO могут быть выполнены. Если вам нужен ленивый бесконечный список всех сообщений, полученных по сокету, вы должны сообщить Haskell априори, где в последовательности IO действий это подходит (если вы не используете unsafeInterleaveIO).

Arrow абстракции вы ищете называется ArrowLoop, но он тоже имеет проблемы с право ужесточением закона для строгих монад.

На первый взгляд, это может выглядеть как (проявляется через mdo или mfix) является решением тоже, но копать немного глубже shows that fixIO has problems, как loop от ArrowLoop.

Однако иногда ограничение на то, что действия IO должны выполняться один за другим, немного чрезмерно, и это то, что для unsafeInterleaveIO.Цитируя docs

unsafeInterleaveIO позволяет IO вычисление быть отложено лениво. Когда передано значение типа IO a, IO будет выполняться только тогда, когда требуется значение a.


Теперь, даже если вы явно сказали, что вы не хотите unsafeInterleaveIO решение, как я надеюсь, удалось убедить вас, что это путь, вот это:

interweavingRepeatM :: IO a -> IO [a] 
interweavingRepeatM action = unsafeInterleaveIO ((:) <$> action <*> interweavingRepeatM action) 

И здесь работает для случайных чисел:

ghci> import System.Random 
ghci> sourceOfRandomness <- interweavingRepeatM randomIO :: IO [Integer] 
ghci> take 10 sourceOfRandomness 
[-2002742716261662204,7803971943047671004,-8395318556488893887,-7372674153585794391,5906750628663631621,6428130029392850107,6453903217221537923,-8966011929671667536,6419977320189968675,-1842456468700051776] 
+0

Большое спасибо за это объяснение. Не могли бы вы подробнее остановиться на следующем: 1. Как решить эту проблему для общей монады, преобразованной из «IO»? 2. Почему метод «mdo» дал повторение значения? –

+1

@DouglasVieira 1. Я не думаю, что вы можете в целом - монады предназначены для кодирования этой последовательности секвенирования 2. 'mdo' desugars использовать' fixIO' и 'fixIO' смутно оценивает только его действие _o nce_ (смотря на [источник] (https://hackage.haskell.org/package/base-4.9.1.0/docs/src/System.IO.html#fixIO) может быть полезно). Даже если вам удалось написать что-то, что действительно должно было вернуть список, вы бы столкнулись с еще более запутанным сообщением об ошибке MVAR (или, что еще хуже, - зависающей программой). – Alec

+0

@DouglasVieira Что касается 1. - Я вижу, что связанный с вами пакет также предоставляет функции 'IO'. Вы можете использовать эти и 'unsafeInterleaveIO' для получения своего списка, а затем' liftIO' обратно в свою желаемую монаду. – Alec

3

Alec's answer охватывает у наш общий вопрос. Ниже приводится конкретно о кабелепровод и аналогичные потоковые библиотеки.

Я также попытался Conduit подход, который не работал ни даже в случае Identity:

import Conduit 

runConduitPure $ yieldMany [1..] .| sinkList >>= return . take 5 

Хотя потоковые библиотеки обычно используются, чтобы избежать рода трудности вы упоминаете (сравни вступительные замечания Pipes.Tutorial), предполагается, что вы будете использовать их типы потоков вместо списков. Рассмотрим, например, как sinkList описывается в документации Conduit:

Потреблять все значения из потока и возврата в виде списка. Обратите внимание, что это вытащит все значения в память.

Это означает, что с помощью sinkMany сразу после yieldMany возвращает вас на круги свои: Собирают все значения в память именно то, что делает сочетание sequence, IO и бесконечные списки непригодных. Вместо этого вы должны использовать инфраструктуру потоковой библиотеки для построения этапов вашего конвейера. Вот несколько простых примеров использования в основном готовые вещей из трубопровода и трубопроводных-комбинаторы:

GHCi> import Conduit 
GHCi> runConduitPure $ yieldMany [1..] .| takeC 5 .| sinkList 
[1,2,3,4,5] 
GHCi> runConduit $ yieldMany [1..] .| takeC 5 .| printC -- try it without takeC 
1 
2 
3 
4 
5 
GHCi> runConduit $ yieldMany [1..] .| takeC 5 .| scanlC (+) 0 .| printC 
0 
1 
3 
6 
10 
15 
GHCi> :{ 
GHCi| runConduit $ yieldMany [1..] .| takeC 5 
GHCi|  .| awaitForever (\x -> liftIO (print (2*x)) >> yield x) 
GHCi|  .| printC 
GHCi| :} 
2 
1 
4 
2 
6 
3 
8 
4 
10 
5 
GHCi> runConduit $ (sourceRandom :: Producer IO Int) .| takeC 5 .| printC 
1652736016140975126 
5518223062916052424 
-1236337270682979278 
8079753510915129274 
-609160753105692151 

(спасибо Michael заставили меня заметить sourceRandom - сначала я закатилась моим с repeatMC randomIO.)

+0

Спасибо за разъяснение относительно 'Conduit'. Проблема, однако, в том, что я хочу, чтобы решение работало над произвольной функцией 'f :: [a] -> b', которая действительно работает с (бесконечными) списками. Поэтому, как вы указали, «Conduit» - это не то решение, которое я ищу. Хуже того, как указал @Alec, в общем случае нет решения. Чтобы дать больше контекста, я пытался работать над простой реализацией функционально-реактивного программирования, где примитивный тип «Event :: Ord t => [(t, a)]» - это бесконечный список, упорядоченный по времени (т.е. я фактически хотел 'f :: Event -> b'). –

+0

Функции 'f', которые вы на самом деле упоминаете -« (например, взять 5, takeWhile (<100). Scanl (+) 0 и т. Д. ») - это элементарные потоковые преобразования, которые действительно работают на бесконечных потоках. Вы не указали причину, по которой вы используете бесконечные списки, а не потоки, которые поддерживают почти весь api 'Data.List', не требуя« последовательности »и co. Эта последовательность не может быть спасена «в общем случае» - это весь смысл. – Michael

 Смежные вопросы

  • Нет связанных вопросов^_^