Search code examples
haskellzoomingcoroutinehaskell-lens

Zoom instance for Coroutine (Await Int) (State MyState) Int


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)?


Solution

  • 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)