这是Ben's previous answer的后续内容。我已经要求对X t
行动"需要清理的情况进行类型检查" (完成后取消按钮和/或键盘)。他的回答是一个monadic包装器NeedsCleanup
,我目前的实现是这样的:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype NeedsCleanup m t =
NeedsCleanup
{
-- | Escape hatch from the NeedsCleanup Monad;
-- returns the original action.
original_action :: m t
}
deriving (Functor, Applicative, Monad)
-- | executes unclean_action and cleans up afterwards.
-- (cleanedUp action) is a normal X() action
cleanedUp :: NeedsCleanup X t -> X t
cleanedUp unclean_action = do
result <- original_action unclean_action
doCleanup
return result
这样,如果action
的类型为NeedsCleanup X ()
,我就不会意外地将其用作X ()
,而不会先通过(cleanedUp action)
发送。NeedsCleanup
奇妙!
我希望改进NeedsCleanup X ()
包装器,以便它也可以&#34; monadically&#34;传递数据,表明究竟需要清理什么。
这是因为,我发现,不同的NeedsCleanup X t
行动可能需要清理不同的事情,而且我必须在所有绑定在一起之后进行清理。
更准确地说,对于每个CleanupData
操作,我希望与data CleanupData = CleanupData
{
keyboard_needs_cleanup :: Bool
, buttons_needing_cleanup :: Set.Set Buttons
-- any other fields
-- ...
}
相关联:
CleanupData
两个-- | combines two CleanupData into the resulting CleanupData
combineCleanupData :: CleanupData -> CleanupData -> CleanupData
combineCleanupData dta1 dta2 =
CleanupData
{
keyboard_needs_cleanup =
(keyboard_needs_cleanup dta1) || (keyboard_needs_cleanup dta2)
, buttons_needing_cleanup =
(buttons_needing_cleanup dta1) `Set.union` (buttons_needing_cleanup dta2)
-- union other data fields
-- ...
}
可以组合在一起,大致形成一个联合(&#34;之后,你必须为这些动作和#34清理它们。)。
action1 :: NeedsCleanup X ()
例如,如果:
dta1 :: CleanupData
与action2 :: NeedsCleanup X ()
dta2 :: CleanupData
与action1 >> action2
然后,combineCleanupData dta1 dta2
应该与cleanedUp :: NeedsCleanup X t -> X t
相关联(大致&#34;您需要清理两者&#34;)。
最后,最后,函数X t
应执行基础CleanupData
操作并获取操作import Control.Monad.Writer.Lazy (WriterT(..), runWriterT, tell, MonadWriter(..))
import Control.Monad.Trans.Class (MonadTrans(..))
import Data.Monoid (Monoid(..))
initialCleanupData =
CleanupData
{
keyboard_needs_cleanup = False
, buttons_needing_cleanup = Set.empty
-- initial values for other fields
}
instance Monoid CleanupData where
mempty = initialCleanupData
mappend = combineCleanupData
newtype NeedsCleanup m t =
NeedsCleanup
{
to_writable :: WriterT CleanupData m t
} deriving (MonadTrans, Monad, Applicative, Functor, MonadIO, MonadWriter CleanupData)
cleanup :: NeedsCleanup X t -> X t
cleanup action = do
(ret_val, cleanup_data) <- runWriterT (to_writable action)
-- clean up based on cleanup_data
-- ...
return ret_val
(以查看需要清理的内容)。
是否可以使用monadic包装器以这种方式跟踪数据?
更新
我最终使用类似于Ilmo Euro的答案,除了为CleanupData定义Monoid结构而不是使用List Monoid。类似于:
tell
为了定义需要清理的操作,我会CleanupData
它的needsCleanup_GrabButton
:: MonadIO m => Display -> Window -> Button -> NeedsCleanup m ()
needsCleanup_GrabButton dply window button = do
liftIO $ grabButton dply button anyModifier window True buttonReleaseMask grabModeAsync grabModeAsync none none
tell cleanup_data
where
-- the stuff we need to clean up from this
-- particular action
cleanup_data = initialCleanupData
{
buttons_needing_cleanup = Set.singleton button
}
,例如,类似于:
{{1}}
答案 0 :(得分:1)
例如,您可以使用Writer
monad:
import Control.Monad.Writer
data DirtyThing = Keyboard | Mouse
newtype Dirty m a = Dirty { unDirty :: WriterT [DirtyThing] m a }
doFoo :: Dirty IO ()
doFoo = -- doing something dirty
cleanup :: Dirty m a -> m a
cleanup action = do
(val, dirtyThings) <- runWriterT (unDirty action)
-- cleanup dirtyThings
return val
为了提高效率,您可以使用Set
而不是列表(并使用适当的Monoid
实例为其定义新类型包装器)。另一种更安全(但更繁琐)的方法是使用indexed monads。