0

假设我有一个计算

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函数可能不适合单子计算。对于状态转换器,它将被迫忘记程序状态的任何变化。

似乎可以创建一个更通用的方法,该方法也采用提升函数,并导致计算t () -> t ()t提升(转换)单子在哪里。

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 风格的代码?谢谢!!

4

1 回答 1

2

首先,忘记那个class业务,看起来你只想要一个功能。

这个问题由Monad*类:MonadIOMonadState等解决。因此,如果您有一个可以执行 IO 的单子计算,但允许执行其他操作,您可以将m任何可以执行 IO 操作的单子作为类型参数:

foo :: (MonadIO m) => m () -> m ()
foo x = do
    liftIO $ putStrLn "prefix"
    x
    liftIO $ putStrLn "suffix"

现在它是什么并不重要m,因为MonadIO说如何将其提升回您想要的操作。

面对新的转换器,这些Monad*类有些非模块化——您需要的实例数量是单子转换器数量的二次方。这个问题有各种次优的解决方案。如果这些事情与您有关,您可以随时具体化该类:

foo :: (Monad m) => (forall a. IO a -> m a) -> m () -> m ()
foo lift x = do
    lift $ putStrLn "prefix"
    x
    lift $ putStrLn "suffix"

是否这样做取决于您的抽象级别。如果您正在编写一个用于构建内容代码的库,您将需要前者,如果您正在编写一个用于构建其他库代码的库,则可能需要后者。无论如何,这有点棘手,这一切都是因为 monad 堆栈不通勤。

于 2012-02-16T01:40:54.247 回答