int-index / ether

Monad Transformers and Classes
https://int-index.github.io/ether/
BSD 3-Clause "New" or "Revised" License
78 stars 7 forks source link

Add zoom #3

Closed gittywithexcitement closed 8 years ago

gittywithexcitement commented 9 years ago

What about adding a zoom function for implicitly and explicitly tagged transformers?

int-index commented 9 years ago

How do you imagine it would work? Currently you can use zoom from lens, see this comment.

gittywithexcitement commented 9 years ago

I see. That comment covers the case where the callee function has a mtl MonadState constraint. As far as I know, it does not help with:

The simplest use case is when the callee function uses 1 Ether state transformer. I could use mtl state transformer instead, but I prefer uniformity in my code over a mix of Ether and mtl state transformers.

My example use case is a caller function that uses 2 (implicitly or explicitly) tagged Ether state transformers, and a callee that uses one of the states untouched, and a lens applied to the other state.

import           Control.Lens.Type                (Lens')
import qualified Control.Monad.Ether.State.Strict as EtherE
import qualified Control.Monad.Ether.Implicit.State.Strict as EtherI

caller :: (EtherI.MonadState (Sum Int) m, EtherI.MonadState Text m)
      => m Int
caller = zoomE (Proxy:: Proxy (Sum Int)) (Proxy:: Proxy Int) _Wrapped' callee

callee :: (EtherI.MonadState (Int) m, EtherI.MonadState Text m)
      => m Int
callee = EtherI.get

Here's what I came up with:

zoomE :: EtherE.MonadState tagBig stateBig m
      => proxy tagBig
      -> proxy tagSmall
      -> Lens' stateBig stateSmall
      -> EtherE.StateT tagSmall stateSmall m b
      -> m b
zoomE tagBig_ tagSmall_ lens st = do
  sBig0 <- EtherE.get tagBig_
  let sSmall0 = view lens sBig0
  (a, sSmall1) <- EtherE.runStateT tagSmall_ st sSmall0
  let sBig1 = set lens sSmall1 sBig0
  EtherE.put tagBig_ sBig1
  pure a

zoomI :: EtherI.MonadState stateBig m
      => Lens' stateBig stateSmall
      -> EtherI.StateT stateSmall m b
      -> m b
zoomI lens st = do
  sBig0 <- EtherI.get
  let sSmall0 = view lens sBig0
  (a, sSmall1) <- EtherI.runStateT st sSmall0
  let sBig1 = set lens sSmall1 sBig0
  EtherI.put sBig1
  pure a

Let me know what you think. If this looks reasonable to you I can make a PR and do edits.

int-index commented 9 years ago

Your solution is specific to StateT. I need to explore the design space. I definitely don't want to incur a dependency on lens for this package, but I'm afraid a good zoom implementation would require it.

I have a couple of ideas, though.

int-index commented 8 years ago

I've developed a solution that works nicely using reflection. I have no intention to include it in ether but it surely can go into a separate package ether-zoom. Ping me if you're still interested.

gittywithexcitement commented 8 years ago

I am interested, @int-index. Did you ever make ether-zoom?

int-index commented 8 years ago

No, but here's the general idea:

You can redirect instance search using DispatchT in the style of tagAttach and tagReplace. Declare a type like data K_TagZoom t z = TagZoom t z and use the z parameter to reify a lens: Reifies z (Lens' stateOuter stateInner). Use this lens to define the appropriate MonadState instance:

instance ( MonadState tag stateOuter m
         , Reifies z (Lens' stateOuter stateInner)
         ) => MonadState tag stateInner (DispatchTagZoomT tag z m) where
  get = ... -- use the lens here
  put = ... -- use the lens here
  state = ... -- use the lens here

I'll probably package this up when I'm done with ether-flatten, but no promises. PRs welcome.

int-index commented 8 years ago

I realized that there are some non-trivial pitfalls and implemented it myself, see #26. Tell me what you think.