Haskell extension: flexible contexts
1. Flexible program
Open FlexibleContexts
extension can release the power of monad transformers, a simple example here
import Control.Monad.Except import Control.Monad.State foo :: (MonadIO m) => (MonadError String m) => (MonadState Int m) => m () foo = do liftIO $ putStrLn "hello" modify (+ 1) throwError "error"
This program prints hello
, then modify its state, finally throw an error, these effects are able, since the type class constraints say the function has IO
, State Int
, and Except String
.
2. Instantized function
Therefore, we can instantized the function:
foo' :: ExceptT String (StateT Int IO) () foo' = foo foo'' :: StateT Int (ExceptT String IO) () foo'' = foo
We can take a look at how to invoke these functions
main :: IO () main = do (a, s) <- runStateT (runExceptT foo') 0 putStrLn $ show a putStrLn $ show s
The output should be:
hello Left "error" 1
Even the program is failed, you still can get the state! Reversely, foo''
will give a different result:
main :: IO () main = do b <- runExceptT $ runStateT foo'' 0 putStrLn $ show b
This program will drop the state, if the program failed, the whole program failed. The result is
hello 1
3. Conclusion
With exchanging, we can see how does the contexts been flexible. The order of transformers is not trivial, it can affect the behavior of the program. Two transformers bring two order, three bring six order, although not every permutation is useful or has affect, but a flexible program maybe more polymorphic than one think. That maybe will not be your expectation, and hence, you might need to export a stable interface for your module, but inside of your module? Just let it free.