Claim: the classes are factored the wrong way!
- Dominant language
- Haskell
- Stars
- 57
- Forks
- 38
- PR merge metrics
- No merged PRs in 30d
Description
I want to make a probably-contraversial claim and proposal. :-)
The classes are currently factored based on how the various utils are implemented. For example `bracket` uses `mask` etc internally, so it gets the `MonadMask` class constraint. I claim this is backwards.
Look at it as a heirarchy this way:
1. `(...) => m ()` I'm writing code that needs work safely despite the fact that environment that has exceptions, but I never need to throw exceptions myself.
2. `(...) => m ()` I'm writing code that needs to throw exceptions (and work safely in the presence of exceptions), but I never need to catch exceptions myself.
3. `(...) => m ()` I'm writing code that needs to throw and catch exceptions, and work safely in the presence of exceptions, including async exceptions, but I never need to use `mask` & co directly.
4. `(...) => m ()` I'm doing some funky stuff and need to use masking directly.
So down at the bottom of the heirarchy we dont' need to throw or catch, we just need to work in an environment where other code can throw. So all we need here is `bracket` and `finally`, to manage effects/state in the presense of exceptions.
At the top end, 99% of my application code never needs to make direct use of `mask` and co. This is good, since any use of masking is hard and really needs to be audited carefully. Most app code lives at level 1 and 2 there, with some at 3 and none at 4 (if one can at all help it).
So this is why the current heirarchy is backwards. It puts `bracket`, the least offensive thing, into the `MonadMask` sin bin. I want to audit all uses of `mask`, and probably even uses of `catch` need some review. The class heirarchy now is no good in helping with constraining the effects app code can do.
Sure, it makes sense based on the implementation, but I think the reverse view, of use is more compelling. It's the same reason we use classes and non-IO types, to restrict the range of effects possible, and reduces the junior-programmer-shoots-self-in-foot danger, or at least lets us focus audit.
How would this be done? Easy, just make `bracket` and co be members of the class, rather than separately defined functions, It's still possible to provide lots of defaults so that defining instances is still easy.
Example implementation of this approach: https://github.com/input-output-hk/ouroboros-network/blob/master/io-sim-classes/src/Control/Monad/Class/MonadThrow.hs Note that this uses the same 3-class heirarchy, though the logical conclusion of my argument above would be a 4-class heirarchy, or Y-shaped hirarchy, e.g. with a `MonadBracket`.
And here's another example highlighting the `bracket` issue: `STM`. I want to be able to write code that can run in various context, including `IO` and `STM`. So I want to use `bracket` to handle things safely. But now I can't run this thing in `STM` because that requires `MonadMask`! But it's easy to write `bracket` for `STM`, but the current approach prevents it.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.