MongoDB Переполнение стека

87 views
Skip to first unread message

Александр Замараев

unread,
Oct 10, 2014, 6:17:45 AM10/10/14
to haskell...@googlegroups.com
Пишу простенький код:

-- file: mongo_get_test.hs
{-# LANGUAGE OverloadedStrings #-}
import Control.Exception (bracket)
import Control.Monad.Trans (liftIO)
import Database.MongoDB

main = do
  bracket
    (connect $ host "127.0.0.1")
    close
    (\pipe -> access pipe master dbname run)

run = do
  docs <-rest =<< find (select [] clname)
  liftIO $ putStrLn "Opa" >> mapM_ (print . exclude ["_id"]) docs

Компилю и запускаю:
$ ghc -O2 --make mongo_get_test.hs
$ ./mongo_get_test
                                                                                                    
Получаю переполнение стека:
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.

Я ожидал получение ленивого списка.
Но, такое впечатление, что rest пытается всосать в себя всю коллекцию.
Что с этим можно сделать?
Есть ли штатный способ получить желаемое поведение?

Maxim Taldykin

unread,
Oct 10, 2014, 9:24:00 AM10/10/14
to haskell...@googlegroups.com
Почему так происходит вполне понятно. Если посмотреть на определения:

rest :: (MonadIO m, MonadBaseControl IO m) => Cursor -> Action m [Document]
-- ^ Return remaining documents in query result
rest c = loop (next c)

loop :: (Functor m, Monad m) => m (Maybe a) -> m [a]
-- ^ Repeatedy execute action, collecting results, until it returns Nothing
loop act = act >>= maybe (return []) (\a -> (a :) <$> loop act)

видно, что loop рекурсивно вызывается через (>>=), а он скорее всего
не ленивый (думаю это справедливо почти для всех инстансов MonadIO).

Что с этим делать?
Можно вывод результатов положить внутрь loop.

run = do
cur <- find (select [] clname)
let loop' = next cur >>= \case
Nothing -> return ()
Just doc -> liftIO (print $ exclude ["_id"] doc) >> loop'
liftIO $ putStrLn "Opa"
loop'


10 октября 2014 г., 14:17 пользователь Александр Замараев
<tonal.p...@gmail.com> написал:
> --
> Вы получили это сообщение, поскольку подписаны на группу "Русский Haskell".
> Чтобы отменить подписку на эту группу и больше не получать от нее сообщения,
> отправьте письмо на электронный адрес
> haskell-russi...@googlegroups.com.
> Чтобы отправлять сообщения в эту группу, отправьте письмо на электронный
> адрес haskell...@googlegroups.com.
> Чтобы посмотреть обсуждение на веб-странице, перейдите по ссылке
> https://groups.google.com/d/msgid/haskell-russian/214644a9-9ba8-4b13-a149-9ceebd3501e3%40googlegroups.com.
> Чтобы настроить другие параметры, перейдите по ссылке
> https://groups.google.com/d/optout.

Maxim Taldykin

unread,
Oct 10, 2014, 11:33:49 AM10/10/14
to haskell...@googlegroups.com
Или можно loop сделать с аккумулятором и хвостовой рекурсией.

10 октября 2014 г., 17:23 пользователь Maxim Taldykin
<jor...@gmail.com> написал:

nponeccop

unread,
Oct 11, 2014, 2:42:40 AM10/11/14
to haskell...@googlegroups.com


On Friday, October 10, 2014 1:17:45 PM UTC+3, Александр Замараев wrote:
Пишу простенький код:
                                                                                                   
Получаю переполнение стека:

Я ожидал получение ленивого списка.

Сейчас рекомендуется вместо Lazy IO использовать Iteratee IO http://www.haskell.org/haskellwiki/Iteratee_I/O

Т.е. рекомендованное решение - использовать http://hackage.haskell.org/package/conduit-1.2.0.2 или аналогичные библиотеки, перечисленные по ссылке выше. Мне кондуит понравился больше всего.

Maxim Taldykin

unread,
Oct 11, 2014, 11:59:57 AM10/11/14
to haskell...@googlegroups.com
11 октября 2014 г., 10:42 пользователь nponeccop
<andy.m...@gmail.com> написал:
>
>
> On Friday, October 10, 2014 1:17:45 PM UTC+3, Александр Замараев wrote:
>>
>> Пишу простенький код:
>>
>> Получаю переполнение стека:
>>
>> Я ожидал получение ленивого списка.
>
>
> Сейчас рекомендуется вместо Lazy IO использовать Iteratee IO
> http://www.haskell.org/haskellwiki/Iteratee_I/O

Категорически поддерживаю.

С помощью lazy IO можно оперировать большими данными не храня их
целиком в памяти и, при этом, делать это модульным (composable)
образом. Вот только lazy IO плохо дружит побочными эффектами.

Оба предложенных мною решения ленивый ввод/вывод не используют,
поэтому первое не composable, а второе (loop с аккумулятором) --
хранит весь результат в памяти.

Iteratee позволяет убить всех трёх зайцев сразу.
Мне больше нравится http://hackage.haskell.org/package/pipes
Выглядит это примерно так:

...
import Pipes hiding (next)
...

run = do
docs <- rest' <$> find (select [] clname)
liftIO $ putStrLn "Opa"
runEffect $ for docs (liftIO . print . exclude ["_id"])

rest' c = loop' (next c)

loop' :: (Functor m, Monad m) => m (Maybe a) -> Producer a m ()
loop' act = lift act >>= \case
Nothing -> return ()
Just res -> yield res >> loop' act

Александр Замараев

unread,
Oct 14, 2014, 6:26:52 AM10/14/14
to haskell...@googlegroups.com
Спасибо всем откликнувшимся.
Я использую первый предложенный вариант, т. к. предпологается простенькая утилитка перекидывающая данные из mongodb в RabbitMQ практически без обработки.
Код получается вполне прямолинейным, память не забивается и скорость ~ 5 килозаписей в секунду - вполне приличная. :)

Но было бы интересно почитать краткое сравнение Pipes с Conduit-ами, тем более что на русском ничего про Pipes не находится...

П. С. Для меня синтаксис \case - незнаком, хотя из использования удобство вполне очивидно.
Некоторое время пришлось покапатся в нете, чтобы найти что это такое и как оно включается. :)
Если кто, как и я не в курсе, то:
Называется LambdaCase.
Включается соответствующей прагмой: {-# LANGUAGE LambdaCase #-}

суббота, 11 октября 2014 г., 22:59:57 UTC+7 пользователь Maxim Taldykin написал:
Reply all
Reply to author
Forward
0 new messages