在将一元命令作为参数的函数中支持 monad 转换器的最佳方法是什么?



假设我有一个计算

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是提升(转换)的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样式代码?谢谢

首先,忘记class业务,它看起来就像你只想要一个函数。

这个问题由Monad*类来解决:MonadIOMonadState等。因此,如果你有一个可以执行IO但允许执行其他操作的单元计算,你可以将任何可以执行IO操作的单体作为类型参数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堆栈不通勤。

最新更新