Time for my weekly lens question;
I have a monad stack:
newtype Action a = Action
{ runAct :: StateT ActionState (ReaderT Hooks IO) a
} deriving (Functor, Applicative, Monad, MonadState ActionState, MonadReader Hooks, MonadIO)
data ActionState = ActionState
{ _ed :: Editor
, _asyncs :: [AsyncAction]
}
I used makeClassy
on the Editor
type to generate a HasEditor
typeclass which my editor lenses depend on.
An Editor
has many Buffer
s; I've defined another monad stack type for an action that acts over a specific buffer (a BufAction
); the only difference is that the StateT is over a Buffer
:
newtype BufAction a = BufAction
{ runBufAct::StateT Buffer (ReaderT Hooks IO) a
} deriving (Functor, Applicative, Monad, MonadState Buffer, MonadReader Hooks, MonadIO)
to run a BufAction
I use zoom (editor.buffers.ix selected)
to zoom the StateT to a specific buffer; but the issue is that now inside the BufAction
I can no longer use any lenses that operate over editor
or require HasEditor
.
Ideally all Action
s run inside a BufAction
without lifting, while BufAction
s cannot run inside an Action
. In this case BufAction
would require the full ActionState
, but also a reference to a specific buffer in order to run; whereas Action
just requires the ActionState
; so BufAction
is a more restrictive Monad and Action
s should be embeddable in it.
So approximately I want some sort of type like this:
newtype Action a = forall s. HasEditor s => Action
{ runAct :: StateT s (ReaderT Hooks IO) a
} deriving (Functor, Applicative, Monad, MonadState s, MonadReader Hooks, MonadIO)
However GHC chokes on this; it can't handle existentials and constraints in a newtype;
I switched it to a data
type; but then I lose GeneralizedNewtypeDeriving and need to implement all of those deriving
clauses manually; which I'd really rather not do.
I've also tried using a type alias; which would mean I don't need to derive typeclasses, but since I also embed Actions
in other datatypes I run into errors; for example since I use Action
here:
data ActionState = ActionState
{ _ed :: Editor
, _asyncs :: [Async (Action ())]
, _hooks :: Hooks
, _nextHook :: Int
}
I run into:
• Illegal polymorphic type: Action ()
GHC doesn't yet support impredicative polymorphism
• In the definition of data constructor ‘ActionState’
In the data type declaration for ‘ActionState’
Taking a different tact; I've also tried implementing a flexible MonadState instance:
instance (HasEditor s, HasBuffer s) => (MonadState s) BufAction where
but get:
• Illegal instance declaration for ‘MonadState s BufAction’
The coverage condition fails in class ‘MonadState’
for functional dependency: ‘m -> s’
Reason: lhs type ‘BufAction’ does not determine rhs type ‘s’
Un-determined variable: s
• In the instance declaration for ‘(MonadState s) BufAction’
Because MonadState uses functional dependencies...
Really stuck on this one and I could use a hand!
Thanks for taking a look! I really appreciate the help!
It looks like this is what you're trying to do to me. The constraints about what kind of state is acceptable in an Action will be specified on definitions using Action, not action itself. Then you'll be able to use the zoom
function from the lens package to focus on different Buffer
s in your Editor
, for example.
{-# Language TemplateHaskell #-}
{-# Language GeneralizedNewtypeDeriving #-}
{-# Language MultiParamTypeClasses #-}
{-# Language FlexibleInstances #-}
{-# Language TypeFamilies #-}
{-# Language UndecidableInstances #-} -- for the Zoomed type instance
module Demo where
import Control.Monad.State
import Control.Monad.Reader
import Control.Lens
data Hooks
data SomeState = SomeState
{ _thing1, _thing2 :: Int }
makeLenses ''SomeState
newtype Action s a = Action
{ runAct :: StateT s (ReaderT Hooks IO) a
} deriving (Functor, Applicative, Monad, MonadState s)
instance Zoom (Action a) (Action s) a s where
zoom l (Action m) = Action (zoom l m)
type instance Zoomed (Action s) = Zoomed (StateT s (ReaderT Hooks IO))
example :: Action Int a -> Action SomeState a
example = zoom thing1