How do I use zoom with a MonadState constraint of a newtype?

520 views Asked by At

I have two functions, one with a MonadState constraint of the wrapped type, the other has a MonadState constraint of the unwrapped type. I'd like to seamlessly run the second function within the first.

For example:

import qualified Control.Monad.State.Strict as MTL
import Data.Monoid (Sum)
import Control.Lens.Zoom (zoom)
import Control.Lens.Wrapped (_Wrapped')

outer :: (MTL.MonadState (Sum Int) m) => m Int
outer = zoom _Wrapped' inner

inner :: (MTL.MonadState Int m) => m Int
inner = MTL.get

The above seems to me like it should work, yet I get 3 type checker errors. The first:

Could not deduce (Control.Lens.Zoom.Zoom m0 m Int (Sum Int))
  arising from a use of ‘zoom’
from the context (MTL.MonadState (Sum Int) m)
  bound by the type signature for
             outer :: MTL.MonadState (Sum Int) m => m Int
The type variable ‘m0’ is ambiguous

From my searching, I get the impression that zoom can't do what I want. (found this quote on http://ircbrowse.net/browse/haskell "reltuk: yeah, thats the unfortunate downside of lenses is that zooming forces you to a concrete state monad") I guess that lines up with the error message stating that "m0 is ambiguous".

I'd really rather not change my MonadState constraint into a concrete monad.

Is there some other standard way of doing what I want?

Edit:

This will not type check:

sumOuter :: (Functor (Zoomed m Int),
           Zoom m m Int t,
           Wrapped t,
           Unwrapped t ~ Int,
           MTL.MonadState (Sum Int) m) => m Int
sumOuter = zoom _Wrapped' sumInner

sumInner :: (MTL.MonadState Int m) => m Int
sumInner = MTL.get
1

There are 1 answers

1
András Kovács On BEST ANSWER

zoom has its own class for overloading, so no wonder just MonadState doesn't cut it. The Zoom class covers roughly the same ground as mtl, although it has somewhat more complicated machinery. In any case, you're not obligated to program in concrete monads.

You can try enabling NoMonomorphismRestriction and inferring a type:

{-# LANGUAGE NoMonomorphismRestriction #-}

import Control.Lens.Internal.Zoom
import Control.Lens
import Control.Monad.State
import Data.Monoid
import Control.Monad.Except -- for illustration

outer = zoom _Wrapped' get

Now :t outer gives

outer :: 
  (Functor (Zoomed m (Unwrapped t)), Zoom m n (Unwrapped t) t, Wrapped t) 
  => n (Unwrapped t)

This isn't pretty, but it seems to work.

> runState outer (Sum 10)
(10, Sum {getSum = 10})
> runState (runExceptT outer) (Sum 10) :: (Either String Int, Sum Int)
(Right 10,Sum {getSum = 10})

EDIT:

If you really want to specialize to Sum Int as the outer state, and also want to have MonadState (Sum Int) n constraint, this suffices:

sumOuter ::
  (Functor (Zoomed m Int), Zoom m n Int (Sum Int)) => n Int
sumOuter = zoom _Wrapped' sumInner

sumInner :: (MTL.MonadState Int m) => m Int
sumInner = MTL.get

What about the MonadState constraint? There's no need to write it out, because Zoom m n s t has MonadState s m and MonadState t n as superclasses.

Likewise, the more general outer function already implies MonadState t n.