z1naOK9nu8iY5A
z1naOK9nu8iY5A

Reputation: 893

Reusing sinkFile with Haskell Conduit

I want to send a list of Photos to a sink to save them on the disk. Each Photo structure contains a content field of type lazy ByteString.

Basically I want to do something like that:

CL.sourceList photos $$ sinkPhotos

I would like to reuse CB.sinkFile in my sink since, if I understand correctly, it does error management. Here is my attempt so far:

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

It fails with:

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 }

Upvotes: 2

Views: 255

Answers (1)

Michael
Michael

Reputation: 2909

I'm not sure I'm following all the questions you ask. I'll just address the type question: the error is only arising because of the signature you supplied. Doesn't this work?

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

It is now just a question of finding the right place to use runResourceT, no? Querying ghci we see:

    >>> 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 () 

So given a list of Photos, you can write

 main = runResourceT $ CL.sourceList photos $$ sinkPhotos 

Edit: By the way, the type queries in ghci give clearer results if you use the type

 sinkPhotos :: Sink Photo (ResourceT IO) ()

though you may have reason to allow for other possibilities.

Upvotes: 3

Related Questions