假设我有一个计算
class A where
foo :: () -> ()
instance A IO where
foo x = do
print "prefix"
x
print "suffix"
现在,假设我想写
instance A => A (MyMonadTransformerT γ )
然后,在实施foo
时,我被迫“解开”其论点,例如foo x = lift (foo (unlift x))
。这个unlift
函数对于monadic计算可能不好。对于状态变换器,它将被迫忘记程序状态的任何变化。
似乎可以创建一个更通用的方法,它也需要一个提升函数,并导致计算t () -> t ()
,其中t
是提升(转换)的monad。
class Monad => A' where
foo' :: Monad t =>
(forall z . z -> t z) -- lifting function
-> t ()
-> t ()
foo :: () -> ()
foo = foo' id
instance A' IO where
foo' lift x = do
lift (print "prefix")
x
lift (print "suffix")
instance A' => A' (StateT γ ) 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
问题。这是最好的方法吗?有没有更普遍的东西可以写? CPS风格的代码?谢谢!
答案 0 :(得分:2)
首先,忘记class
业务,看起来你只想要一个功能。
Monad*
类解决了这个问题:MonadIO
,MonadState
等等。所以如果你有一个monadic计算可以做IO,但是允许做其他事情,您可以将任何可执行IO操作的monad作为类型参数m
:
foo :: (MonadIO m) => m () -> m ()
foo x = do
liftIO $ putStrLn "prefix"
x
liftIO $ putStrLn "suffix"
现在m
是什么并不重要,因为MonadIO
说明如何将其提升回您想要的操作。
Monad*
类在新变换器面前有点非模块化 - 你需要的实例数量是monad变换器数量的二次方。这个问题有各种次优的解决方案。如果这些事情与您有关,您可以随时通知该课程:
foo :: (Monad m) => (forall a. IO a -> m a) -> m () -> m ()
foo lift x = do
lift $ putStrLn "prefix"
x
lift $ putStrLn "suffix"
是否这样做取决于您的抽象级别。如果您正在编写用于构建内容代码的库,那么您将需要前者,如果您正在编写用于构建其他库代码的库,则可能需要后者。不管怎样,这都很棘手,因为monad堆栈不能通勤。