20-CS-4003-001 Organization of Programming Languages Fall 2017
Exceptions

Lambda calculus, Type theory, Formal semantics, Program analysis

    Prev     Next     All lectures           Code

catch


  {- for ioeGetErrorString,
     isDoesNotExistError, ioError
     ioeGetFileName -}
  import System.IO.Error
  {- for try -}
  import Control.Exception

  f1 x  = catch (getFileNamed x) handler

  getFileNamed :: FilePath -> IO ()
  getFileNamed fileName =
    do {
      contents <- readFile fileName  ;
        putStrLn $ "The file has " ++
           show (length (lines contents)) ++
           " lines!"
    }

  handler :: IOError -> IO ()
  handler e
    | isDoesNotExistError e =
      case ioeGetFileName(e) of
        Just path ->
          putStrLn ("File " ++
                     path ++ " " ++
                     ioeGetErrorString(e))
        Nothing ->
          putStrLn ("File " ++
                     ioeGetErrorString(e))
    | isPermissionError e =
      case ioeGetFileName(e) of 
        Just path -> 
          putStrLn ("Permission denied")
        Nothing ->   
          putStrLn ("File " ++ 
                     ioeGetErrorString(e))
    | otherwise = ioError e
  -   Pure code can throw exceptions which can only be caught in the I/O part of code, inside a do block. This is because it cannot be determined when, or even if, an error condition will be evaluated due to the laziness of the language. I/O code execution, though, is deterministic.

Function f1 demonstates the above. This function calls catch with two arguments. The first is a code block to try, and the second is the name of a function (a handler) to execute if some exception is thrown. The code block is a do expression. Suppose file types.hs exists, then

  *Main> f1 "types.hs"
  The file has 109 lines!

but, if there is no file named asdf then

  *Main> f1 "asdf"
  File asdf does not exist

If test.fil exists but without read permission, the following happens:

  *Main> f1 "test.fil"
  Permission denied