Haskell / XMonad:围绕Monad的包装器,它也可以跟踪数据

时间:2015-05-17 15:46:00

标签: haskell wrapper monads xmonad

这是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 :: CleanupDataaction2 :: NeedsCleanup X ()

相关联

dta2 :: CleanupDataaction1 >> 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}}

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