Reputation: 907
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?
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
Upvotes: 1
Views: 524
Reputation: 30103
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
.
Upvotes: 2