final-0.1: utility to add extra safety to monadic returns

Safe HaskellSafe-Inferred

Control.Final.Example

Description

Consider this peace of code:

 join $ atomically $ do
   radarPositive <- readTVar radarTVar
   launchKeyInserted <- readTVar launchKeyTVar
   case radarPositive of
     False -> do
       modifyTVar radarNegativeCounter (+1)
       return $ print "No need for missiles, it's peaceful"
     True -> do
       modifyTVar radarPositiveCounter (+1)
       case launchKeyInserted of
         False -> do
           modifyTVar keyMissingCounter (+1)
           return $ print "No launch key, ignoring radar"
         True -> do
           modifyTVar launchCounter (+1)
           return $ launchMissiles
   return $ print "extra debug: state checking finished"

We use STM to make state checking of multiple TVars one atomic transaction. Since we can't do IO in STM, we are just returning the IO that needs to be done in the different cases. Unfortunately when we try to add the extra debugging as the last statement, that silently ignores all the previous "return" values, even launchMissiles.

On the other hand when using final:

 atomicJoinFinal $ do
   radarPositive <- readTVar radarTVar
   launchKeyInserted <- readTVar launchKeyTVar
   case radarPositive of
     False -> do
       modifyTVar radarNegativeCounter (+1)
       final $ print "No need for missiles, it's peaceful"
     True -> do
       modifyTVar radarPositiveCounter (+1)
       case launchKeyInserted of
         False -> do
           modifyTVar keyMissingCounter (+1)
           final $ print "No launch key, ignoring radar"
         True -> do
           modifyTVar launchCounter (+1)
           final $ launchMissiles
   final $ print "extra debug: state checking finished"

We get a compile error that contains this:

    Note: there are several potential instances:
      instance FinalClass Control.Final.FinalTooManyReturns

Internally Final is based on ambiguity checking in the type system. The prohibited ambiguity occurs, because the only way to decide what final means is by matching it to the corresponding atomicJoinFinal. This is now only possible for the final at the end of the function and not for the middle ones, so we get the error.