thanks again for the help!
I'm making extensive use of the E. Kmett's Lens library, to avoid X/Y problems I'll explain a bit of context.
I'm working on an extensible text editor and want to provide extension writers with a monad DSL, an Alteration is a monad transformer stack with a StateT over the Store type, which basically stores the whole text editor. Inside the Store is an Editor which has Buffers. Users can specify an Alteration to act over the whole store, but to simplify things I also provide a BufAction which operates over just a single buffer.
I was planning on implementing this by using a helper called bufDo which runs a BufAction over each Buffer, and a focusDo which runs a BufAction on the 'focused' Buffer. Here's some context:
data Store = Store
{ _event :: [Event]
, _editor :: E.Editor
, _extState :: Map TypeRep Ext
} deriving (Show)
data Editor = Editor {
_buffers :: [Buffer]
, _focused :: Int
, _exiting :: Bool
} deriving Show
data Buffer = Buffer
{ _text :: T.Text
, _bufExts :: Map TypeRep Ext
, _attrs :: [IAttr]
}
newtype Alteration a = Alteration
{ runAlt :: StateT Store IO a
} deriving (Functor, Applicative, Monad, MonadState Store, MonadIO)
newtype BufAction a = BufAction
{ runBufAction::StateT Buffer IO a
} deriving (Functor, Applicative, Monad, MonadState Buffer, MonadIO)
Here's my proposed implementations for bufDo and focusDo:
bufDo :: ???
bufDo = zoom (buffers.traverse)
-- focusedBuf is a Lens' over the focused buffer (I just 'force' the traversal using ^?! in case you're wondering)
focusDo :: ???
focusDo = zoom focusedBuf
This makes sense in my head and gets close to type-checking, but when I try to add a type for them I get a bit confused, ghc suggests a few things and I ended up with this, which is far from elegant:
bufDo :: (Applicative (Zoomed BufAction ()), Zoom BufAction Alteration Buffer Store) => BufAction () -> Alteration ()
focusDo :: (Functor (Zoomed BufAction ()), Zoom BufAction Alteration Buffer Store) => BufAction () -> Alteration ()
Which makes ghc happy for those definitions, but when I try to actually use either of them I get these errors:
- No instance for (Functor (Zoomed BufAction ()))
arising from a use of ‘focusDo’
- No instance for (Applicative (Zoomed BufAction ()))
arising from a use of ‘bufDo’
Looking around it seems like I may need to specify an instance for Zoom, but I'm not sure quite how to do that.
Anyone have ideas? I'd also love it if you could explain why I need a Zoom instance (if that's the case).
Cheers!
It seems that there is a
Zoomedtype family that is used to specify what kind of "effect" we will have when we zoom. In some cases, theZoomedtype instance for a monad transformer appears to piggyback on theZoomedfor the underlying monad, for exampleGiven that
AlterationandBufActionare just newtypes over a state transformer, perhaps we could do the same:Then we must provide the
Zoominstance.Zoomis a multi-parameter typeclass and the four parameters seem to be original monad, zoomed out monad, original state, zoomed out state:We just unwrap
BufAction, zoom with the underlying monad, and wrap asAlteration.This basic test typechecks:
I believe you could avoid defining the
Zoominstance and have a special-purposezoomBufActionToAlterationfunctionBut then if you have a lot of different zoomable things it can be a chore to remember the name of each zoom funcion. That's where the typeclass can help.