VTY-UI 需要 IO。我能让这件事发生吗?
VTY-UI needs IO. Can I make this happen?
我正在尝试使用 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
from 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
约束,但是 ReaderT
和 StateT
上面 IO
已经有那个类型类的实例。
正如 liftBaseOpDiscard
的文档中提到的,回调中对状态的更改将被丢弃。
MonadBaseControl
允许您暂时将 monad 堆栈的上层隐藏到堆栈的基本 monad 的值中 (liftBaseWith
) and afterwards pop them again, if needed (restoreM
)。
编辑: 如果我们需要保留回调中发生的效果(比如状态的变化),一种解决方案是使用 "mimic" 状态 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)
monad 之后可以 运行 使用 runRWSIORefT
。
状态部分:你可以使用这个模块。感谢那些意识到使 get
和 put
多态化是个好主意的人。
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
module IState where
import Control.Monad
import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Trans.Class
import Control.Applicative
import Data.IORef
newtype IState s m a = IState (ReaderT (IORef s) m a)
runIState (IState a) s = do
sr <- liftIO $ newIORef s
runReaderT a sr
runIStateRef (IState a) r = runReaderT a r
instance (Monad m) => Monad (IState s m) where
return = IState . return
(IState a) >>= f = let
f' i = let (IState i') = f i in i'
in IState (a >>= f')
instance (Monad m,Functor m) => Applicative (IState s m) where
pure = return
(<*>) = ap
instance (Functor m) => Functor (IState s m) where
fmap f (IState a) = IState (fmap f a)
instance (MonadIO m) => MonadIO (IState s m) where
liftIO = lift . liftIO
instance (MonadState s' m) => MonadState s' (IState s m) where
get = lift get
put = lift . put
-- Because of this instance IState is almost a drop-in replacement for StateT
instance (MonadIO m) => MonadState s (IState s m) where
get = IState $ do
r <- ask
liftIO $ readIORef r
put v = IState $ do
r <- ask
liftIO $ writeIORef r v
instance MonadTrans (IState s) where
lift a = IState (lift a)
我成功实现了问题评论中提到的建议。
我在 IO
中提供 vty 回调,将事件发送到 Chan
。然后我有另一个线程监听这些事件并在我自己的 monad 中执行适当的操作。
我正在尝试使用 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
from 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
约束,但是 ReaderT
和 StateT
上面 IO
已经有那个类型类的实例。
正如 liftBaseOpDiscard
的文档中提到的,回调中对状态的更改将被丢弃。
MonadBaseControl
允许您暂时将 monad 堆栈的上层隐藏到堆栈的基本 monad 的值中 (liftBaseWith
) and afterwards pop them again, if needed (restoreM
)。
编辑: 如果我们需要保留回调中发生的效果(比如状态的变化),一种解决方案是使用 "mimic" 状态 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)
monad 之后可以 运行 使用 runRWSIORefT
。
状态部分:你可以使用这个模块。感谢那些意识到使 get
和 put
多态化是个好主意的人。
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
module IState where
import Control.Monad
import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Trans.Class
import Control.Applicative
import Data.IORef
newtype IState s m a = IState (ReaderT (IORef s) m a)
runIState (IState a) s = do
sr <- liftIO $ newIORef s
runReaderT a sr
runIStateRef (IState a) r = runReaderT a r
instance (Monad m) => Monad (IState s m) where
return = IState . return
(IState a) >>= f = let
f' i = let (IState i') = f i in i'
in IState (a >>= f')
instance (Monad m,Functor m) => Applicative (IState s m) where
pure = return
(<*>) = ap
instance (Functor m) => Functor (IState s m) where
fmap f (IState a) = IState (fmap f a)
instance (MonadIO m) => MonadIO (IState s m) where
liftIO = lift . liftIO
instance (MonadState s' m) => MonadState s' (IState s m) where
get = lift get
put = lift . put
-- Because of this instance IState is almost a drop-in replacement for StateT
instance (MonadIO m) => MonadState s (IState s m) where
get = IState $ do
r <- ask
liftIO $ readIORef r
put v = IState $ do
r <- ask
liftIO $ writeIORef r v
instance MonadTrans (IState s) where
lift a = IState (lift a)
我成功实现了问题评论中提到的建议。
我在 IO
中提供 vty 回调,将事件发送到 Chan
。然后我有另一个线程监听这些事件并在我自己的 monad 中执行适当的操作。