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 Buffer
s. 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
Zoomed
type family that is used to specify what kind of "effect" we will have when we zoom. In some cases, theZoomed
type instance for a monad transformer appears to piggyback on theZoomed
for the underlying monad, for exampleGiven that
Alteration
andBufAction
are just newtypes over a state transformer, perhaps we could do the same:Then we must provide the
Zoom
instance.Zoom
is 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
Zoom
instance and have a special-purposezoomBufActionToAlteration
functionBut 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.