Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The "handle" function and Real World Haskell

I'm reading RWH, and I've come to chapter 9. It introduces the following piece of code:

import System.IO
import Control.Exception

saferFileSize :: FilePath -> IO (Maybe Integer)
saferFileSize path = handle (\_ -> return Nothing) $ do
  h <- openFile path ReadMode
  size <- hFileSize h
  hClose h
  return (Just size)

It won't compile however, giving the following error message:

test.hs:5:22:
    Ambiguous type variable `e0' in the constraint:
      (Exception e0) arising from a use of `handle'
    Probable fix: add a type signature that fixes these type variable(s)
    In the expression: handle (\ _ -> return Nothing)
    In the expression:
      handle (\ _ -> return Nothing)
      $ do { h <- openFile path ReadMode;
             size <- hFileSize h;
             hClose h;
             return (Just size) }
    In an equation for `saferFileSize':
        saferFileSize path
          = handle (\ _ -> return Nothing)
            $ do { h <- openFile path ReadMode;
                   size <- hFileSize h;
                   hClose h;
                   .... }

What is going wrong here? Why won't it compile?

like image 222
Undreren Avatar asked May 14 '12 06:05

Undreren


1 Answers

Not too long after RWH came out, the exception interface was changed to support more flexible handlers where the type of the handler determines which exceptions it will catch. E.g. a handler which takes SomeException will catch anything (not usually a good idea), while a handler that takes IOException will only catch IO exceptions.

As a consequence of this, it's easy to run into ambiguity problems with "do-nothing" handlers like the one in your example, since the compiler can't infer what type of exceptions you're trying to catch. An easy way of fixing this is to provide a type signature for your handler function.

handle ((\_ -> return Nothing) :: IOException -> IO (Maybe Integer)) $ do ...

Though, this can be somewhat verbose. An alternative solution is to specialize handle.

handleIO :: (IOException -> IO a) -> IO a -> IO a
handleIO = handle

Then, you can just use handleIO whenever you want to handle IO exceptions, without having to spell out the type signature of the handler.

saferFileSize path = handleIO (\_ -> return Nothing) $ do ...

A third option is to use the ScopedTypeVariables extension, which (among other things) allows you to provide a type annotation for just the argument of a function, allowing the rest to be inferred.

{-# LANGUAGE ScopedTypeVariables #-}
saferFileSize path = handle (\(_ :: IOException) -> return Nothing) $ do ...
like image 141
hammar Avatar answered Oct 11 '22 04:10

hammar