Network.firstSuccesful: 'throw' vs 'throwIO' usage

Simon Marlow marlowsd at gmail.com
Thu Sep 6 15:21:46 CEST 2012


On 06/09/2012 14:07, Roman Cheplyaka wrote:
> * Simon Marlow <marlowsd at gmail.com> [2012-09-06 12:35:52+0100]
>> On 06/09/2012 11:05, Roman Cheplyaka wrote:
>>> * Herbert Valerio Riedel <hvr at gnu.org> [2012-09-06 11:40:23+0200]
>>>> Hello,
>>>>
>>>> while reading over the source code of network[1], I noticed a use of 'throw' where I'd
>>>> expect 'throwIO':
>>>>
>>>>      import qualified Control.Exception as Exception
>>>>
>>>>      catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a
>>>>      catchIO = Exception.catch
>>>>
>>>>      -- Returns the first action from a list which does not throw an exception.
>>>>      -- If all the actions throw exceptions (and the list of actions is not empty),
>>>>      -- the last exception is thrown.
>>>>      firstSuccessful :: [IO a] -> IO a
>>>>      firstSuccessful [] = error "firstSuccessful: empty list"
>>>>      firstSuccessful (p:ps) = catchIO p $ \e ->
>>>>          case ps of
>>>>              [] -> Exception.throw e
>>>>              _  -> firstSuccessful ps
>>>>
>>>>
>>>>
>>>> ...so, is `throw` used properly in the code above, or should it rather
>>>> be `throwIO`?
>>>>
>>>>
>>>>   [1]: http://hackage.haskell.org/packages/archive/network/2.3.1.0/doc/html/src/Network.html#firstSuccessful
>>>
>>> In this particular situation it doesn't matter.
>>>
>>> If you use throwIO, then, if all actions fail, firstSuccesful will
>>> return a proper IO action which, when sequenced, throws an exception.
>>>
>>> If you use throw, then in the same situation the result of
>>> firstSuccessful will throw an exception before yielding a proper IO
>>> value.
>>>
>>> However, I agree with you that throwIO would be somewhat more idiomatic
>>> here. (And IIRC I wrote this code, so you can blame me.)
>>
>> Here is some background reading:
>>
>> http://hackage.haskell.org/trac/ghc/ticket/1171
>>
>> The bottom line is that it's hard to tell what will happen if you use
>> throw here.  Always use throwIO if you can.
>
> So, regarding this example, does it mean that, under some circumstances,
> `firstSuccessful [a]` can throw `error "firstSuccessful: empty list"`?

I think this is the case with GHC as it stands, although it isn't clear 
whether we want that behaviour or not (see the ticket).  It boils down 
to this

   case ps of
      [] -> throw e
      _  -> error "firstSuccessful..."

Now suppose GHC floated out one of the case branches:

   let x = error "firstSuccessful..."
   case ps of
      [] -> throw e
      _  -> x

and now the strictness analyser can prove that x is strict, because the 
case expression has value _|_.  So it evaluates x early, and you get an 
unexpected exception.

Cheers,
	Simon




> Would you also advise changing `error` to `throwIO . ErrorCall` here?
>




More information about the Libraries mailing list