Build my own monad transformer module hiding the main monad
I am learning mtl and I want to know the correct way to create new monads as modules (not as a typical application use).
As a simple example, I wrote ZipperT
monad here ( full code ):
{-# LANGUAGE FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, GeneralizedNewtypeDeriving #-}
module ZipperT (
MonadZipper (..)
, ZipperT
, runZipperT
) where
import Control.Applicative
import Control.Monad.State
class Monad m => MonadZipper a m | m -> a where
pushL :: a -> m ()
pushR :: a -> m ()
...
data ZipperState s = ZipperState { left :: [s], right :: [s] }
newtype ZipperT s m a = ZipperT_ { runZipperT_ :: StateT (ZipperState s) m a }
deriving ( Functor, Applicative
, Monad, MonadIO, MonadTrans
, MonadState (ZipperState s))
instance (Monad m) => MonadZipper s (ZipperT s m) where
pushL x = modify $ \(ZipperState left right) -> ZipperState (x:left) right
pushR x = modify $ \(ZipperState left right) -> ZipperState left (x:right)
...
runZipperT :: (Monad m) => ZipperT s m a -> ([s], [s]) -> m (a, ([s], [s]))
runZipperT computation (left, right) = do
(x, ZipperState left' right') <- runStateT (runZipperT_ computation) (ZipperState left right)
return (x, (left', right'))
it works and i can compose with other monads
import Control.Monad.Identity
import Control.Monad.State
import ZipperT
length' :: [a] -> Int
length' xs = runIdentity (execStateT (runZipperT contar ([], xs)) 0)
where contar = headR >>= \x -> case x of
Nothing -> return ()
Just _ -> do
right2left
(lift . modify) (+1)
-- ^^^^^^^
contar
But I want to avoid being explicit lift
.
- What is the correct way to create such modules?
- Can you avoid the explicit
lift
? (I want to hide the inner structure ofStateT
mineZipperT
)
Thank!
source to share
I think if you can write an instance MonadState
for your transformer, you can use modify
without lift
:
instance Monad m => MonadState (ZipperT s m a) where
...
I have to admit, I'm not sure which part of the condition modify
should be affected, though.
I have looked at the complete code. It seems that you have already identified
MonadState (ZipperState s) (ZipperT s m)
This already provides modify
, which, however, changes the wrong baseline. What you really wanted was to expose the state wrapped in m
, provided that it is MonadState
. In theory, this could be done with
instance MonadState s m => MonadState s (ZipperT s m) where
...
But now we have two instances MonadState
for the same monad, resulting in a conflict.
I guess I solved it somehow.
Here's what I did:
First, I deleted the original instance deriving MonadState
. Instead, I wrote
getZ :: Monad m => ZipperT s m (ZipperState s) getZ = ZipperT_ get putZ :: Monad m => ZipperState s -> ZipperT s m () putZ = ZipperT_ . put modifyZ :: Monad m => (ZipperState s -> ZipperState s) -> ZipperT s m () modifyZ = ZipperT_ . modify
and replaced the previous entries get,put,modify
in the library ZipperT
with the above UDFs.
Then I added a new instance:
-- This requires UndecidableInstances
instance MonadState s m => MonadState s (ZipperT a m) where
get = lift get
put = lift . put
And now the client code works without elevators:
length' :: [a] -> Int
length' xs = runIdentity (execStateT (runZipperT contar ([], xs)) 0)
where contar :: ZipperT a (StateT Int Identity) ()
contar = headR >>= \x -> case x of
Nothing -> return ()
Just _ -> do
right2left
modify (+ (1::Int))
-- ^^^^^^^
contar
source to share