Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the transformer come as the first argument of "run" functions?

I mean why doesn't it come the last?

Because of this convention to evaluate a transformer stack one has to write an awkward thing like that:

runStateT (runReaderT (runRWST stack r s) r') s'

instead of:

runStateT s' $ runReaderT r' $ runRWST r s $ stack

And combining it with immediate do becomes all the more awkward:

let 
  action = do
    liftIO $ putStrLn "Do"
    liftIO $ putStrLn "something"
  in runStateT (runReaderT (runRWST action r s) r') s'

instead of:

runStateT s' $ runReaderT r' $ runRWST r s $ do
  liftIO $ putStrLn "Do"
  liftIO $ putStrLn "something"

Does this have any motivation behind or is this just an unfortunate convention?

BTW, I do realize that the current convention makes it easy to implement the "run" function using the record syntax, but this can't be an argument, since libraries must prefer usability to easiness of implementation.

like image 321
Nikita Volkov Avatar asked Mar 27 '14 06:03

Nikita Volkov


2 Answers

In the HaskellWiki entry for Parameter order and in this Stack Overflow question, there are two recommendations for parameter order:

  • The "most varying" argument should generally go last.
  • Put the data structure last.

In my experience with the Reader/State monad, The same computation is invoked on different environments/states more frequently than different monadic computations are invoked on the same environment/state. The current parameter order is convenient for that.

Another reason: Reader/State monadic values behave very much like functions that take an environment/the initial state as parameters. And in Haskell functions go before parameters.

To avoid the necessity of nested ReaderT/StateT/RWST transformers, you can work on with a single RWST transformer carrying global state/environment, and use zoom and magnify from the lens library to adapt computations that work in more restricted environments.

like image 117
danidiaz Avatar answered Sep 27 '22 19:09

danidiaz


I'm sure there's an element of expedience to it. It's very easy to define

newtype StateT = StateT { runStateT :: s -> m (a, s) }

and be done with it.

like image 43
Tom Ellis Avatar answered Sep 27 '22 18:09

Tom Ellis