Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use catch with IO Handle

Tags:

haskell

I'm trying to build a little program in Haskell in Leksah that will convert an .lhs file to an .hs file. But I can't get it to build without errors. Any help would be appreciated. I'm new to Haskell so forgive me if the answer is obvious, though I can't see it. Here is the code and error:

-- Converts .lhs (literary Haskell files) to .hs (plain Haskell files)
-- Keeps only the statements which are normally compiled, plus blank lines.

-- To use:
--    ghc --make lhs2hs.hs
-- to get an executable file lhs2hs.
-- Then
--    lhs2hs filename
-- will open filename.lhs and save the converted file in filename.hs

-- by Scot Drysdale on 7/28/07, based on SOE program on p. 241

module Main where
import System.IO
-- import System.IO.Error (catchIOError)
import Control.Exception (catch)
import System.Environment   -- to allow getArgs

-- Opens a file, given name and mode
openGivenFile :: String -> IOMode -> IO Handle
openGivenFile name mode
  =  catch (do handle <- openFile name mode
               return handle) 
            handler
            where
              handler :: IOError -> IO Handle 
              -- Next line does not match IO Handle
              handler ex = putStrLn $ "Caught exception: " ++ show ex
                                --   (\e -> error ("Cannot open " ++ name))

main = do args <- getArgs
          fromHandle <- openGivenFile (args !! 0 ++ ".lhs") ReadMode
          toHandle <- openGivenFile (args !! 0 ++ ".hs") WriteMode
          convertFile fromHandle toHandle
          hClose fromHandle
          hClose toHandle

-- Converts all the lines in a file
convertFile :: Handle -> Handle -> IO ()
convertFile fromHandle toHandle
  = catch (do line <- hGetLine fromHandle
              case line of
                          ('>' : ' ' : rest) -> hPutStrLn toHandle rest
                          ('>' : rest)       -> hPutStrLn toHandle rest
                          ('\n' : rest)      -> hPutStrLn toHandle line
                          ('\r' : rest)      -> hPutStrLn toHandle line 
                          _                  -> return ()
              convertFile fromHandle toHandle)
              handler
              where
               handler :: IOError -> IO ()
               handler ex = putStrLn $ "Caught exception: " ++ show ex

Error is :

 src\Main.hs:27:28-69:
    Couldn't match type `()' with `Handle'
    Expected type: IO Handle
      Actual type: IO ()
    In the expression: putStrLn $ "Caught exception: " ++ show ex
    In an equation for `handler':
        handler ex = putStrLn $ "Caught exception: " ++ show ex
    In an equation for `openGivenFile':
        openGivenFile name mode
          = catch
              (do { handle <- openFile name mode;
                    return handle })
              handler
          where
              handler :: IOError -> IO Handle
              handler ex = putStrLn $ "Caught exception: " ++ show ex
like image 528
te7 Avatar asked Dec 21 '25 01:12

te7


1 Answers

A signature such as

openGivenFile :: String -> IOMode -> IO Handle

states that openGivenFile returns an Handle, unless it throws an exception.

If you decide to catch IOErrors inside that, that's fine by itself, but you need some way to come up with an Handle in every case -- which is impossible.

So, either you need to let the exception flow out if it, or you need to change the signature so that you no longer promise an Handle but something weaker. E.g.

openGivenFile :: String -> IOMode -> IO (Either IOError Handle)
openGivenFile name mode
  =  catch (do handle <- openFile name mode
               return (Right handle)) 
            handler
            where
              handler :: IOError -> IO (Either IOError Handle)
              handler ex = do
                 -- since we are returning the error,
                 -- printing it may be a bad design now
                 putStrLn $ "Caught exception: " ++ show ex
                 return (Left ex)

After some cleanup:

openGivenFile :: String -> IOMode -> IO (Either IOError Handle)
openGivenFile name mode = (Right <$> openFile name mode) `catch` handler
   where handler :: IOError -> IO (Either IOError Handle)
         handler = return . Left

Even this would work:

openGivenFile :: String -> IOMode -> IO (Either IOError Handle)
openGivenFile name mode =
   (Right <$> openFile name mode) `catch` (return . Left)

but the exception being catched is a now a bit hidden inside the type signature of the whole function. Since this is an one-liner, it should be clear enough, but I'd prefer the previous alternative in general since it document the intent of catching IOError better.

like image 81
chi Avatar answered Dec 22 '25 14:12

chi