haskell / cabal

Official upstream development repository for Cabal and cabal-install
https://haskell.org/cabal
Other
1.63k stars 695 forks source link

Reader Monad for Flags #4492

Open angerman opened 7 years ago

angerman commented 7 years ago

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:

angerman commented 7 years ago

@hvr pointed out the following #1940 for previous discussion of the Shell Monad

ezyang commented 7 years ago

A few notes:

phadej commented 7 years ago

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).

hvr commented 7 years ago

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).

angerman commented 7 years ago

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, to ReaderT/IO, to ReaderT/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.

BardurArantsson commented 7 years ago

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 :).

fgaz commented 3 years ago

@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

andreasabel commented 3 years ago

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