fused-effects / fused-effects

A fast, flexible, fused effect system for Haskell

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Simpler `liftWith` type?

mitchellwrosen opened this issue · comments

When unlifting a withMVar with liftWith I played a bit of type tetris:

2019-10-29-015016_409x140_scrot

but it seems to me that the only way to call the second argument is by applying it to m <$ ctx.

Could liftWith therefore have this simpler type?

2019-10-29-015339_735x113_scrot

which would change the above code to

2019-10-29-015523_404x88_scrot

The type is more general in order to allow you to run multiple actions in sequence. E.g. if you have:

m :: m a
k :: a -> m b

then you need to handle both, with the latter fmap’d into the context resulting from the former:

liftWith $ \ ctx hdl ->
  hdl (m <$ ctx)   -- ctx is only used to lift the initial action
  >>= hdl . fmap k -- subsequent actions must use the derived context

The simpler type signature you suggest makes the correct implementation of this behaviour much harder, requiring a liftWith call per action, and makes it much easier to accidentally reset the context to its original value at every bind. Edit: Indeed, you might not have any control over the bind at all, because something in another library might be performing it instead.

For this simple example, we could have done hdl ((m >>= k) <$ ctx) instead, which is indeed amenable to a simpler liftWith. However, that isn’t generally the case, e.g. if you need to run IO actions in between m and k to e.g. time or log something.

Part of my goal with the design of liftWith is to make it a little easier to avoid, or at least to notice, the typical MonadBaseControl problem where you restore the wrong state, likely without even realizing. But without linear types I can’t enforce that you only use the input context once, and even if I could, it would be wrong; consider Control.Exception.catch which needs to use that context for both the initial action and the error handler, but then use the derived context for the continuation. (Exactly the same thing applies to thread, incidentally, as can be seen in Control.Effect.Catch’s Effect instance.)

Thinking a little further, I don’t want to include another function with the simplified type, since the same argument about the potential for misuse applies.

Therefore, I’m going to close this out for now.