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:

Left "error"

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


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.

Date: 2023-05-10 Wed 13:00
Author: Lîm Tsú-thuàn