Wejn
Wejn

Reputation: 356

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

I'm poking the XMonad.Actions.WindowMenu module from xmonad-contrib, trying to make it configurable.

And I have hard time understanding the following bit:

In the original code is a construct like:

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

What is the best way to go about understanding what's going on there? In other words, in what context is the whole tags <- asks (workspaces . config) evaluated?

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]

it blows up with an error:

    • 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]

Edited to add:

I understand the types of that statement:

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

but the reason it breaks is (still) a mystery.

Upvotes: 1

Views: 440

Answers (4)

Daniel Wagner
Daniel Wagner

Reputation: 152707

The simplest thing is to just have both windowMenu and defaultActions operate in the X monad, which already has the appropriate MonadReader instance. So:

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

No need for the shenanigans in the other answer of making windowMenu' be a parameterized higher-order function. If you really want to make a windowMenu' that lets you parameterize on the list of actions, do that directly:

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

In such a world, you can run defaultActions and pass its result to windowMenu' with (>>=) (or more do notation):

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

Upvotes: 2

Wejn
Wejn

Reputation: 356

Ok, thanks to duplode's answer I was able to figure it out:

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

Btw, I couldn't make it work as a X [String] type (not sure why), but the solution above seems to work well enough. Maybe it's not strictly the best (not sure about it), but it's taking me where I want to go.

Upvotes: 1

duplode
duplode

Reputation: 34378

Quoting a comment:

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.

You can use asks to retrieve configuration fields in that way because there is a MonadReader XConf X instance for the X monad. MonadReader is typically used to provide this kind of access to configuration.


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]

Looking at your definition, I suspect you intend defaultActs to be a modified list of workspace tags. That being so, what you want is probably this:

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

That is, obtain the tags from the configuration, produce a modified list, and return it in the context of the X monad.

Upvotes: 1

amalloy
amalloy

Reputation: 91877

Without looking at any documentation, we know this much: this is in the context of do-notation for whatever type is needed by withFocused. It apparently takes a function as argument, and that function must return a monadic value of some type. The value of this do-notation block must have that same type. It certainly does not have the type [(String, X ())]. (Well, okay, it could, since [a] is a monad, but it seems rather unlikely that this is the type that withFocused would expect as result).

You can find out what type it has by looking through the documentation:

withFocused :: (Window -> X ()) -> X ()

Since our do-notation is inside the return value of the lambda for the first parameter, it must have a type of X (). Therefore,

asks (workspaces . config) :: X t

for some t which we could also know by looking up the type of workspaces. The <- binds that t value to the name tags, in the usual way that do-notation does.

Upvotes: 0

Related Questions