Let's say I have a computation
class A m where
foo :: m () -> m ()
instance A IO where
foo x = do
print "prefix"
x
print "suffix"
Now, suppose I want to write
instance A m => A (MyMonadTransformerT γ m)
Then, in implementing foo, I'm forced to "unwrap" its argument, e.g. foo x = lift (foo (unlift x)). This unlift function can be bad for monadic computation. For a state transformer, it would be forced to forget any changes in the program's state.
It seems to work to create a more general method, which also takes a lifting function, and results in computation t () -> t (), where t is the lifted (tranformed) monad.
class Monad m => A' m where
foo' :: Monad t =>
(forall z . m z -> t z) -- lifting function
-> t ()
-> t ()
foo :: m () -> m ()
foo = foo' id
instance A' IO where
foo' lift x = do
lift (print "prefix")
x
lift (print "suffix")
instance A' m => A' (StateT γ m) where
foo' lift' x = foo' (lift' . lift) x
computation :: Num a => StateT a IO ()
computation = do
foo (put 1 >> lift (print "middle"))
v <- get
lift $ print ("value", v)
run_computation :: Num a => IO a
run_computation = execStateT computation 0
Question. Is this the best way? Is there something more general one can write? CPS-style code? Thanks!!
-
2possible duplicate of Lifting a higher order function in HaskellDaniel Wagner– Daniel Wagner2012年02月16日 00:28:18 +00:00Commented Feb 16, 2012 at 0:28
-
Yes, I think it's a duplicate. The "refying the class" approach I and luqui took seems to be the nicest though.gatoatigrado– gatoatigrado2012年02月21日 18:38:20 +00:00Commented Feb 21, 2012 at 18:38
1 Answer 1
First of all, forget that class business, it looks like you just want a function.
This problem is addressed by the Monad* classes: MonadIO, MonadState, etc. So if you have a monadic computation which can do IO, but which is allowed to do other things, you would take as a type parameter m any monad which can perform IO actions:
foo :: (MonadIO m) => m () -> m ()
foo x = do
liftIO $ putStrLn "prefix"
x
liftIO $ putStrLn "suffix"
Now it does not matter what m is, because MonadIO says how to lift it back to the operations you want.
The Monad* classes are somewhat non-modular in the face of new transformers -- the number of instances you need is quadratic in the number of monad transformers. There are various suboptimal solutions to this problem. If such things concern you, you can always reify the class:
foo :: (Monad m) => (forall a. IO a -> m a) -> m () -> m ()
foo lift x = do
lift $ putStrLn "prefix"
x
lift $ putStrLn "suffix"
Whether to do this depends on your level of abstraction. You will want the former if you are writing a library upon which to build content code, and perhaps the latter if you are writing a library upon which to build other library code. It's kind of tricky either way though, all because monad stacks don't commute.
1 Comment
foo', foo should be a function. Besides, the compiler won't be able to infer whether to use lift (1 monad transformer), lift . lift (2 monad transformers), etc. by itself.Explore related questions
See similar questions with these tags.