Reputation: 356
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
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
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
Reputation: 34378
Quoting a comment:
Yes,
windowMenu
hasX ()
as a type, and mydefaultActs
goes for a different type. What I don't understand is how does running the lambda throughwithFocused
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
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