我正在尝试使用 VTY-UI 库构建 UI。
我还使用自定义 monad(一些 monad 相互堆叠)。
对于常规 IO 功能,这不是问题。我可以将它们提升到我的单子(monad)中。但是,VTY-UI 函数 onActivate
具有以下类型签名:
onActivate::Widget Edit -> (Widget Edit -> IO ()) -> IO ()
有没有办法将 Widget Edit -> MyMonad ()
函数转换为 (Widget Edit -> IO ())
,而无需包装和解开我的单子(monad)?
我不想将所有库的类型签名重写为 MonadIO m => m ()
而不是 IO ()
。
最佳答案
函数liftBaseOpDiscard
来自monad-control似乎可以解决问题:
import Control.Monad.Trans.Control
type MyMonad a = ReaderT Int (StateT Int IO) a
onActivate' :: Widget Edit -> (Widget Edit -> MyMonad ()) -> MyMonad ()
onActivate' = liftBaseOpDiscard . onActivate
该函数有一个 MonadBaseControl
约束,但 IO
之上的 ReaderT
和 StateT
已经具有该类型类的实例。
正如 liftBaseOpDiscard
的文档提到的,回调内状态的更改将被丢弃。
MonadBaseControl
允许您暂时将 monad 堆栈的上层隐藏到堆栈的基本 monad 的值中 ( liftBaseWith
),然后在需要时再次弹出它们 ( restoreM
) .
编辑:如果我们需要保留回调内发生的效果(例如状态的更改),一种解决方案是使用 IORef
来“模仿”状态作为 ReaderT
的环境。写入 IORef
的值不会被丢弃。 monad-unlift
包就是围绕这个想法构建的。一个例子:
import Control.Monad.Trans.Unlift
import Control.Monad.Trans.RWS.Ref
import Data.IORef
-- use IORefs for the environment and the state
type MyMonad a = RWSRefT IORef IORef Int () Int IO a
onActivate' :: Widget Edit -> (Widget Edit -> MyMonad ()) -> MyMonad ()
onActivate' we f = do
-- the run function will unlift into IO
UnliftBase run <- askUnliftBase
-- There's no need to manually "restore" the stack using
-- restoreM, because the changes go through the IORefs
liftBase $ onActivate we (run . f)
之后可以使用 runRWSIORefT
运行 monad .
关于haskell - VTY-UI需要IO。我能让这件事发生吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30014884/