ljs.dev
ljs.dev

Reputation: 4493

Converting IO [FilePath] to String or Bytestream

I'm working on this project to get feet wet with Haskell and struggling with finding simple examples.

In this instance, I would like to have a web request handler for Snap, which returns a list of files in a directory.

I believe I'm trying to get the return of getDirectoryContents into a Bytestring which Snap wants.

I am most confused about what to do with the return value I get at the line filenames <- getDirectoryContents "data" below:

import Control.Applicative
import Snap.Core
import Snap.Util.FileServe
import Snap.Http.Server
import System.Directory (getDirectoryContents)

main :: IO ()
main = quickHttpServe site

site :: Snap ()
site =
    ifTop (writeBS "hello world") <|> 
    route [ ("foo", writeBS "bar")
          , ("echo/:echoparam", echoHandler)
          , ("view_root_json_files", listRootFilesHandler)
          ] <|> 
    dir "static" (serveDirectory ".")

echoHandler :: Snap ()
echoHandler = do
    param <- getParam "echoparam"
    maybe (writeBS "must specify echo/param in URL")
          writeBS param

listRootFilesHandler :: Snap ()
listRootFilesHandler = do
    -- read all filenames in /data folders
    filenames <- getDirectoryContents "data"
    writeText filenames

Upvotes: 0

Views: 3296

Answers (2)

Zeta
Zeta

Reputation: 105925

Since you want to use writeText, you need to convert [FilePath] to Text. Luckily, Text is an instance of Monoid, and a list is a instance of Foldable, so we can simply use foldMap pack filenames to get a single text:

-- import Data.Foldable (foldMap)
-- import Data.Text (pack, Text)

toText :: [FilePath] -> Text
toText = foldMap pack

Note that you'll need to use liftIO to actually use a IO a in Snap b, since Snap is an instance of MonadIO:

listRootFilesHandler :: Snap ()
listRootFilesHandler = do
    filenames <- liftIO $ getDirectoryContents "data"
    writeText $ toText filenames

If you want to add a newline (or <br/>) after each FilePath, add flip snoc '\n':

toText = foldMap (flip snoc '\n' . pack)
-- toText = foldMap (flip append (pack "<br/>") . pack)

Upvotes: 6

leftaroundabout
leftaroundabout

Reputation: 120741

You can't "convert" an IO action to a string. Of course not, it's a completely different thing, conceptually. What you can do is extract / "focus" a value in a monad, such as IO. That's what the val <- action syntax in a do block is used for, you have that quite correct.

The only problem with your current implementation is that you have an IO-monad action, but want to execute it in the Snap monad. Well, actually Snap is deep down the IO monad, with a whole lot of extra stuff attached via monad transformers. But you can always use Snap as if it were IO. That's true for a whole bunch of monads (all that are created by stacking transformers on IO), so there's a dedicated class for "monads that can do IO".

listRootFilesHandler = do
    -- read all filenames in /data folders
    filenames <- liftIO $ getDirectoryContents "data"
    ...

The other, rather easier thing is flattening a [FilePath] list to a single string. I suppose you know how to do that.

Upvotes: 2

Related Questions