xmonad `tags <- asks (workspaces .config)` 魔法——我该如何解析它?

xmonad `tags <- asks (workspaces . config)` magic -- how do I parse that?

我正在戳 xmonad-contribXMonad.Actions.WindowMenu 模块,试图使其可配置。

我很难理解以下内容:

original code 中的结构如下:

windowMenu :: X ()
windowMenu = withFocused $ \w -> do
    tags <- asks (workspaces . config)
    -- ...

了解那里发生的事情的最佳方式是什么?换句话说,整个 tags <- asks (workspaces . config) 是在什么情况下评估的?

我问的原因是当我尝试在不同的函数中重构它时:

defaultActs :: [(String, X ())]
defaultActs = do
    tags <- asks (workspaces . config)
    [ ("A: " ++ tag, return ()) | tag <- tags]

它因错误而爆炸:

    • No instance for (MonadReader XConf [])
        arising from a use of ‘asks’
    • In a stmt of a 'do' block: tags <- asks (workspaces . 
      In the expression:
        do tags <- asks (workspaces . config)
           [("A: " ++ tag, return ()) | tag <- tags]
      In an equation for ‘defaultActs’:
          defaultActs
            = do tags <- asks (workspaces . config)
                 [("A: " ++ tag, return ()) | tag <- tags]

编辑添加:

我理解该语句的类型:

ghci> :t asks (workspaces . config)
asks (workspaces . config) :: MonadReader XConf m => m [String]
ghci> :t withFocused
withFocused :: (Window -> X ()) -> X ()

但它破裂的原因(仍然)是个谜。

无需查看任何文档,我们就知道了这么多:这是在 withFocused 需要的任何类型的 do-notation 上下文中。它显然需要一个函数作为参数,并且该函数必须 return 某种类型的 monadic 值。此 do-notation 块的值必须具有相同的类型。它肯定 而不是 具有 [(String, X ())] 类型。 (嗯,好吧,它可以,因为 [a] 是一个 monad,但是这似乎不太可能是 withFocused 期望的结果类型)。

你可以通过查看 documentation:

来找出它的类型
withFocused :: (Window -> X ()) -> X ()

由于我们的 do-notation 位于第一个参数的 lambda 的 return 值内,因此它的类型必须为 X ()。因此,

asks (workspaces . config) :: X t

对于一些 t 我们也可以通过查找 workspaces 的类型来知道。 <- 将那个 t 值绑定到名称 tags,就像 do-notation 所做的那样。

引用评论:

Yes, windowMenu has X () as a type, and my defaultActs goes for a different type. What I don't understand is how does running the lambda through withFocused grant it "access" to some extra context.

您可以使用 asks 以这种方式检索配置字段,因为 the X monad 有一个 MonadReader XConf X 实例。 MonadReader 通常用于提供这种配置访问权限。


The reason I'm asking is that when I try to refactor this in a different function:

defaultActs :: [(String, X ())]
    defaultActs = do
    tags <- asks (workspaces . config)
    [ ("A: " ++ tag, return ()) | tag <- tags]

查看您的定义,我怀疑您打算 defaultActs 成为工作区标签的修改列表。既然如此,你想要的大概是这样的:

defaultActs :: X [String]
defaultActs = do
    tags <- asks (workspaces . config)
    return [ "A: " ++ tag | tag <- tags]

也就是说,从配置中获取标签,生成修改后的列表,然后 return 它在 X monad 的上下文中。

好的,多亏了我才弄明白:

defaultActions :: XConf -> [(String, X ())]
defaultActions = do
    tags <- asks (workspaces . config)
    return ([ ("Cancel menu", return ())
            , ("Close"      , kill)
            , ("Maximize"   , withFocused $ \w -> sendMessage $ maximizeRestore w)
            , ("Minimize"   , withFocused $ \w -> minimizeWindow w)
            ] ++
            [ ("Move to " ++ tag, windows $ W.shift tag) | tag <- tags ])

windowMenu' :: (XConf -> [(String, X ())]) -> X ()
windowMenu' actions = withFocused $ \w -> do
    acts <- asks actions
    Rectangle x y wh ht <- getSize w
    Rectangle sx sy swh sht <- gets $ screenRect . W.screenDetail . W.current . windowset
    let originFractX = (fi x - fi sx + fi wh / 2) / fi swh
        originFractY = (fi y - fi sy + fi ht / 2) / fi sht
        gsConfig = (buildDefaultGSConfig colorizer)
                    { gs_originFractX = originFractX
                    , gs_originFractY = originFractY }
    runSelectedAction gsConfig acts

-- now it composes well, and I can pass in my own `actions` to `windowMenu`
windowMenu = windowMenu' defaultActions

顺便说一句,我无法让它作为 X [String] 类型工作(不知道为什么),但上面的解决方案似乎工作得很好。也许严格来说它不是最好的(不确定),但它带我去我想去的地方。

最简单的事情是让 windowMenudefaultActionsX monad 中运行,它已经有适当的 MonadReader 实例。所以:

defaultActions :: X [(String, X ())]
defaultActions = do
    tags <- asks (workspaces . config)
    return ([ ("Cancel menu", return ())
            , ("Close"      , kill)
            , ("Maximize"   , withFocused $ \w -> sendMessage $ maximizeRestore w)
            , ("Minimize"   , withFocused $ \w -> minimizeWindow w)
            ] ++
            [ ("Move to " ++ tag, windows $ W.shift tag) | tag <- tags ])

windowMenu :: X ()
windowMenu = withFocused $ \w -> do
    acts <- defaultActions
    Rectangle x y wh ht <- getSize w
    Rectangle sx sy swh sht <- gets $ screenRect . W.screenDetail . W.current . windowset
    let originFractX = (fi x - fi sx + fi wh / 2) / fi swh
        originFractY = (fi y - fi sy + fi ht / 2) / fi sht
        gsConfig = (buildDefaultGSConfig colorizer)
                    { gs_originFractX = originFractX
                    , gs_originFractY = originFractY }
    runSelectedAction gsConfig acts

不需要 中使 windowMenu' 成为参数化高阶函数的恶作剧。如果你真的想制作一个 windowMenu' 让你对动作列表进行参数化,直接这样做:

windowMenu' :: [(String, X ())] -> X ()
windowMenu' acts = withFocused $ \w -> do
    Rectangle x y wh ht <- getSize w
    Rectangle sx sy swh sht <- gets $ screenRect . W.screenDetail . W.current . windowset
    let originFractX = (fi x - fi sx + fi wh / 2) / fi swh
        originFractY = (fi y - fi sy + fi ht / 2) / fi sht
        gsConfig = (buildDefaultGSConfig colorizer)
                    { gs_originFractX = originFractX
                    , gs_originFractY = originFractY }
    runSelectedAction gsConfig acts

在这样的世界中,您可以 运行 defaultActions 并将其结果传递给 windowMenu' 使用 (>>=) (或更多 do 表示法):

windowMenu :: X ()
windowMenu = defaultActions >>= windowMenu'
-- OR
windowMenu = do
    acts <- defaultActions
    windowMenu' acts