haskellmonadsio-monadscottylifting

Haskell Monads and the liftIO I don't get it


Hello community thank you for your time.

I have an error and I am not sure what the error is, but what I think the problem is: There is no IO transformer from ext-1.2.4.1:Data.Text.Internal.Lazy.Text IO) to Web.Scotty.Internal.Types.ScottyT.

But I wondering why the compiler works with ext-1.2.4.1:Data.Text.Internal.Lazy.Text IO). That's why I am working just with String and I removed all occurrences of {-# LANGUAGE OverloadedStrings #-} but still get the error. On the other hand, this should be IO [String], shouldn't it? And as you can mention I don't really know what ext-1.2.4.1:Data.Text.Internal.Lazy.Text IO) is.

At another place, I already use liftIO successfully for an a -> IO String function. And I think I use them the same way.

I think I get slowly a feeling for what a monad is, but not quite sure. I don't really know why I have to use a lift function at all.

Error message:

    • No instance for (MonadIO
                         (Web.Scotty.Internal.Types.ScottyT
                            text-1.2.4.1:Data.Text.Internal.Lazy.Text IO))
        arising from a use of ‘liftIO’
    • In a stmt of a 'do' block:
        paths <- liftIO $ getAllFilePaths2 path
      In the expression:
        do paths <- liftIO $ getAllFilePaths2 path
           pathsToScotty paths
      In an equation for ‘pathsToScotty2’:
          pathsToScotty2 path
            = do paths <- liftIO $ getAllFilePaths2 path
                 pathsToScotty paths
   |        
49 |    paths <- liftIO $ getAllFilePaths2  path

Where the error occurred:

import Control.Monad.IO.Class
...
pathsToScotty2 :: String -> ScottyM ()
pathsToScotty2 path = do
   paths <- liftIO $ getAllFilePaths2  path
   pathsToScotty paths

getAllFilePaths2 :: String -> IO [String]
getAllFilePaths2 dir = do
    putStrLn dir
    isFile <- doesFileExist dir 
    if isFile 
        then return [dir] 
        else do
          dirs <- listDirectory dir
          foldl foldHelper2 (return []) $ map (\d -> show $ mconcat [dir, "/",d ]) dirs


foldHelper2 :: IO [String] -> String  -> IO [String]
foldHelper2 ps path = do 
    paths <- ps
    newPaths <- getAllFilePaths2 path
    return (paths ++ newPaths)


Solution

  • Truly understanding monads takes time, practice, and patience, but it shouldn't be too hard to understand the need for liftIO by examining your types.

    First off, the type of liftIO is MonadIO m => IO a -> m a. This means that the function can convert any IO action into an action in the monad m so long as m has an instance of MonadIO. In theory, this can only be implemented if m has some way of processing IO actions, so this function is embedding the given action into the m monad.

    You're definitely in the right sort of place to use liftIO, so why isn't it working? That is, you have a value getAllFilePaths2 path of type IO [String], and you'd like it to be a value of type ScottyM [String] — this indeed seems like a good place to use liftIO. However, ScottyM is not an instance of MonadIO, as that error message you saw is trying to tell you, so you can't use liftIO.

    This may seem crazy—can you really not embed IO actions into ScottyM?—but there's actually a good reason for this. What happens if the IO action throws an error? Does your whole web app crash? It would if you naively used liftIO. Instead, scotty provides the function liftAndCatchIO, which, as the docs describe, is "Like liftIO, but catch any IO exceptions and turn them into Scotty exceptions." This is the preferred way to embed IO actions into Scotty.

    And here comes the final gotcha: Note that liftAndCatchIO actually produces values of type ActionM a, not ScottyM a. Additionally, there's no way to take a value in the ActionM monad and get it into the ScottyM monad. Instead, you need to use that value as an action. So, I'm not sure what pathsToScotty does, but it's very likely that you'll need to rewrite it.