Open angerman opened 7 years ago
@hvr pointed out the following #1940 for previous discussion of the Shell Monad
A few notes:
Introducing a new monad to Cabal library is a massive BC break for basically all Custom client scripts. This might be OK, but you should make the decision knowing you are breaking people, and plan accordingly.
In my experience, all you need is IO plus Reader. But I think that the most important thing is to make the monad abstract.
If it's just IO and Reader, rolling it by hand is really simple, not much code at all. But when we get the Parsec parser we will have a new dep on transformers so you could also use that library.
I'm as well a bit worried about BC break, yet in longer run it will surely be good (if we need to add new contextual value).
See also https://github.com/haskell/cabal/issues/4040#issuecomment-257098590 (which is what lead me to learn about #1940) which is why I'd like to get a Monad in place rather than having to pass around yet another argument (or come up with some other hack).
Finally, would this not insulate us precisely from BC changes in the future? If the underlying Monad evolved (and gains additional functionality), downstream consumers of it would see no breaking changes, but just a more advanced monad, while the existing functionality would stay the same?
I'd agree with @ezyang on a newtype monad, without exposing the stack much. @ndmitchell wrote
The cool thing about Haskell is that I've been able to completely replace the underlying Shake Action monad from
StateT/IO
, toReaderT/IO
, toReaderT/ContT/IO
, without ever breaking any users of Shake. Haskell allows me to produce effective and flexible abstractions.
Which is precisely what we want I believe.
Also relevant: https://www.fpcomplete.com/blog/2017/07/the-rio-monad for a bit of practical experience with a project which is (probably) very similar to cabal-install :).
@andreasabel I think this is the main "monad" ticket, but there are comments about it in other tickets too, eg. https://github.com/haskell/cabal/issues/4357#issuecomment-282463894 https://github.com/haskell/cabal/issues/6977#issuecomment-661075473
My preferred way is to define "service classes" like
class Monad m => MonadOptions m where
askOptions :: m Options
and then functions that need to have access to the options (but are other ways pure) can have types like
foo :: MonadOptions m -> Foo -> m Bar
Rather than with a monolithic "monad for everything", like the Cabal
monad, one can have more control over effects with dedicated service classes. In the end, it could be that the Cabal
monad is the only one to instantiate these services
instance MonadOptions Cabal where
askOptions = ...
but the reader of the code still gets information about which effects they have to worry.
Here is one example from the Agda codebase: https://github.com/agda/agda/blob/6e4fb9d0be1f8236170eac1d7d2514fa10d4c626/src/full/Agda/Interaction/Options/HasOptions.hs#L17-L39
cabal is designed mostly without the use of monads. This leads to functions passing context via arguments along. And even so explicitly.
It seems like there is some interest in having cabal be partially refactored to use a Reader Monad for Flags (global, command specific, and verbosity).
I believe carrying a Record along as
context
instead of passing arguments explicitly might improve the situation, however a Monadic approach would even free one of having to pass the context explicitly.The questions that remains are: