I have a deeply nested state MyState
which is used inside a Coroutine
(from monad-coroutine package):
Coroutine (Await Int) (State MyState) Int
And I like the possibility of using zoom (from lens) to do some manipulation, because it allows me to write something like
zoom (company.assets) $ do
a.b = 3
c.d = 'good'
...
Unfortunately for me I cannot come up with correct declaration of instance Zoom
for the Coroutine
I have.
My attempt to do so was something like this:
instance Zoom (Coroutine z s) (Coroutine z t) s t where
zoom l (Coroutine m) = undefined
But already at this point GHC tells me
Expecting one more argument to ‘s’
The third argument of ‘Zoom’ should have kind ‘*’,
but ‘s’ has kind ‘* -> *’
In the instance declaration for
‘Zoom (Coroutine z s) (Coroutine z t) s t’
But if I do something like
instance Zoom (Coroutine z s) (Coroutine z t) (s a) (t a) where
zoom l (Coroutine m) = undefined
Then GHC is complaining:
Illegal instance declaration for
‘Zoom (Coroutine z s) (Coroutine z t) (s a) (t a)’
The liberal coverage condition fails in class ‘Zoom’
for functional dependency: ‘m -> s’
Reason: lhs type ‘Coroutine z s’ does not determine rhs type ‘s a’
In the instance declaration for
‘Zoom (Coroutine z s) (Coroutine z t) (s a) (t a)’
Now I understand I'm attempting to do something blindly without understanding what is actually expected from me. Anyone could explain the way Zoom
instance is defined for something like my Coroutine
and why it is defined (so not only interested in the correct declaration but also some notes of the thinking process when constructing such declaration)?
By error
Expecting one more argument to ‘s’
The third argument of ‘Zoom’ should have kind ‘*’,
but ‘s’ has kind ‘* -> *’
In the instance declaration for
‘Zoom (Coroutine z s) (Coroutine z t) s t’
GHC is complaining the kind mismatch here
‘Zoom (Coroutine z s) (Coroutine z t) s t’
^ ^
The second type parameter of Coroutine
should be a monad (* -> *
), while s
is the type of the state (*
) as the third type parameter of Zoom
. The instance declaration should be
instance (Functor f, Zoom m n s t) => Zoom (Coroutine f m) (Coroutine f n) s t
Along with some boilerplate to satisfy the context of Zoom
:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
instance (Functor f, MonadState s m) => MonadState s (Coroutine f m) where
get = lift get
put = lift . put
type instance Zoomed (Coroutine s m) = Zoomed m
After a few hours of inspection I realized what I suggested above is impossible to implement, the type of zoom
is too restricted to be used with mapMonad
and I don't know a way to overcome it. However it still will work if everything is monomorphic:
data MyState = MyState
{ _myRecord :: String }
$(makeLenses ''MyState)
zoomMyState :: Functor f => Coroutine f (State String) r -> Coroutine f (State MyState) r
zoomMyState = mapMonad (zoom myRecord)