Повторное использование sinFile с Haskell Conduit

Я хочу отправить список фотографий на приемник, чтобы сохранить их на диске. Каждая структура Photo содержит поле content типа lazy ByteString.

В основном я хочу сделать что-то вроде этого:

CL.sourceList photos $$ sinkPhotos

Я хотел бы повторно использовать CB.sinkFile в моем приемнике, поскольку, если я правильно понимаю, он обрабатывает ошибки. Вот моя попытка:

mkFilePath :: Photo -> FilePath
mkFilePath photo = last $ splitOn "/" (url photo)

sinkPhotos :: Sink Photo IO ()
sinkPhotos = do
    mphoto <- await
    case mphoto of
        Nothing -> return ()
        Just photo -> do
          yield ct =$ CB.sinkFile fp
          sinkPhotos
          where fp = mkFilePath photo
                ct = BL.toStrict $ content photo

Это не удается с:

src/Screepy/Main.hs:30:23:
    No instance for (MonadResource IO)
      arising from a use of `CB.sinkFile'
    Possible fix: add an instance declaration for (MonadResource IO)
    In the second argument of `(=$)', namely `CB.sinkFile fp'
    In a stmt of a 'do' block: yield ct =$ CB.sinkFile fp
    In the expression:
      do { yield ct =$ CB.sinkFile fp;
           sinkPhotos }
  • Как я могу обернуть sinkFile для работы со структурой, а не напрямую с ByteString?
  • Как я могу создать Source из ByteString?

person z1naOK9nu8iY5A    schedule 07.12.2014    source источник


Ответы (1)


Я не уверен, что понимаю все вопросы, которые вы задаете. Я просто отвечу на типовой вопрос: ошибка возникает только из-за предоставленной вами подписи. Разве это не работает?

sinkPhotos :: MonadResource m => Sink Photo m ()
sinkPhotos = do
    mphoto <- await
    case mphoto of
        Nothing -> return ()
        Just photo -> do
          yield ct =$ CB.sinkFile fp
          sinkPhotos
          where fp = mkFilePath photo
                ct = BL.toStrict $ content photo

Теперь вопрос только в том, чтобы найти правильное место для использования runResourceT, не так ли? Запрашивая ghci мы видим:

    >>> let photos = [] :: [Photo]

    >>> :t  CL.sourceList photos $$ sinkPhotos
    CL.sourceList photos $$ sinkPhotos :: MonadResource m => m ()

    >>> :t  runResourceT $ CL.sourceList photos $$ sinkPhotos
    runResourceT $ CL.sourceList photos $$ sinkPhotos
      :: (MonadThrow m, MonadBaseControl IO m, Control.Monad.IO.Class.MonadIO m) =>  m ()

    >>> :t  runResourceT $ CL.sourceList photos $$ sinkPhotos :: IO ()
    runResourceT $ CL.sourceList photos $$ sinkPhotos :: IO () 

Итак, учитывая список фотографий, вы можете написать

 main = runResourceT $ CL.sourceList photos $$ sinkPhotos 

Изменить: кстати, запросы типа в ghci дают более четкие результаты, если вы используете тип

 sinkPhotos :: Sink Photo (ResourceT IO) ()

хотя у вас могут быть причины допустить другие возможности.

person Michael    schedule 08.12.2014
comment
yield ct =$ CB.sinkFile fp вообще верно? - person z1naOK9nu8iY5A; 08.12.2014
comment
Я предположил тип data Photo = Photo {url :: String; content :: L.ByteString}. Тогда это «правильно» в том смысле, что оно проверяет тип. Если вам действительно нужна ленивая строка байтов, вам, вероятно, следует вместо этого написать: CL.sourceList (L.toChunks ct) =$ sinkFile fp чтобы не объединять тексты. Поскольку это фотографии, об этом, возможно, не стоит беспокоиться. Вы также можете просто сделать фотографию content строкой байтов Source. Это будет зависеть от того, откуда они берутся, стоит ли это делать. - person Michael; 08.12.2014
comment
Строгая строка байтов Source на самом деле представляет собой проводную версию ленивой строки байтов. Вопрос в том, застряли ли вы где-то с L.ByteString, потому что библиотека, которую вы используете для получения фотоконтента на странице, использует L.ByteString. Если это так, вероятно, не имеет значения, где в вашем модуле вы «конвертируете» в источник канала. - person Michael; 08.12.2014
comment
Я застрял с L.ByteString, так как здесь я использую wreq и предпочел бы не использовать http-conduit, чтобы не смешивать две сетевые библиотеки. Как бы вы сделали content байтовой строкой Source? - person z1naOK9nu8iY5A; 08.12.2014
comment
CL.sourceList . L.toChunks :: Monad m => L.Text -> Source m S.Text, который представляет собой список (что в основном и представляет собой ленивую строку байтов). Я не вижу специального комбинатора в conduit модулях, которые у меня есть. Также есть что-то вроде L.foldrChunks (\t _ -> yield t) (return ()) :: Monad m => L.Text -> Source m S.Text, которое сворачивается непосредственно к ленивому тексту, исключая использование промежуточного списка, что не так уж важно. - person Michael; 08.12.2014
comment
По какой-то причине я просто тестировал ленивый текст, а не ленивую строку байтов. Но это то же самое: foldrChunks и toChunks также находятся в Data.ByteString.Lazy. - person Michael; 08.12.2014
comment
Спасибо большое, теперь компилируется. Я удивлен, что мой дикий гость yield ct =$ CB.sinkFile fp действительно работал, и что виновником была сигнатура типа. Я предполагаю, что мне все еще нужно больше обучения, чтобы расшифровать ошибки ghc. - person z1naOK9nu8iY5A; 08.12.2014