Search code examples
haskellxmonadxmonad-contrib

XMonad: How to write functions involving the `Query` datatype?


Problem

I have a feeling that the reason for this question is going to ultimately come down to me just being new at/not understanding Haskell, so I'll try to be as detailed as I can with my description, to make it somewhat generic.

I am using a custom XMonad config as my window manager, defined in a file xmonad.hs.

The XMonad manageHook is what I use to handle when certain applications need to open in a certain type of window or on a certain workspace, etc. https://hackage.haskell.org/package/xmonad-0.17.0/docs/XMonad-ManageHook.html

Currently, a bunch of lines in my manageHook in xmonad.hs look like this:

className =? "xxxx" --> doSomething

Here is my current understanding of the line above:

These expressions involve the value of className, defined in XMonad.Hooks.ManageHook, which is of the Query type (Query [Char], to be more specific). https://hackage.haskell.org/package/xmonad-0.17.0/docs/XMonad-ManageHook.html

Query is defined in XMonad.Core. https://hackage.haskell.org/package/xmonad-0.17.0/docs/XMonad-Core.html#t:Query

=? takes the Query [Char] on the left and the [Char] literal on the right and returns a value of type Query Bool; the value will be True if the [Char]s are equal and False otherwise (I think?)

--> takes the Query Bool on the left and an action on the right and executes the action if the value of the Query Bool is True.

https://hackage.haskell.org/package/xmonad-0.17.0/docs/XMonad-ManageHook.html

However, this is only useful to me when I know the exact class name of an application I want to apply a rule to, which is not always the case. So, I took a look at xmonad-contrib to get some ideas as to what other functions I could define.

In XMonad.Hooks.ManageHelpers (https://hackage.haskell.org/package/xmonad-contrib-0.17.0/docs/XMonad-Hooks-ManageHelpers.html), there is ^?, which would be used like below:

className ^? "xxxx" --> doSomething

From my understanding, ^? takes the Query [Char] on the left and the [Char] literal on the right and returns a value type of Query Bool; the value will be True if the [Char] on the left isPrefixOf the [Char] on the right and False otherwise (I think?)

What I would like to create is a new function that is similar, but reversed. Something like,

"xxxx" ?^ className --> doSomething

where ?^ takes the [Char] literal on the left and the Query [Char] on the right and returns a value of type Query Bool; the value should be True if the [Char] on the left isPrefixOf the [Char] on the right and False otherwise. (In other words, I want to define a new function that checks if some string literal is a prefix of the class name, rather than checking if the class name is a prefix of some string literal.)

Initially, I thought this should be easy, but looking at the source code for ^?, I realized there must be something I am fundamentally missing about my understanding of Haskell.

Here is the definition of ^?:

(^?) :: (Eq a, Functor m) => m [a] -> [a] -> m Bool
q ^? x = fmap (`isPrefixOf` x) q

https://hackage.haskell.org/package/xmonad-contrib-0.17.0/docs/src/XMonad.Hooks.ManageHelpers.html#%5E%3F

What I do not understand is how I would write a function that is a reversed version of this. This definitely won't work:

(?^) :: (Eq a, Functor m) => [a] -> m [a] -> m Bool
x ?^ q = fmap (`isPrefixOf` q) q

yet I do not understand why. What property does Query have that (Query [a]) isPrefixOf ([a]) is acceptable, but not ([a]) isPrefixOf (Query [a])?

Furthermore, how can I go about defining a function with the desired behavior? I am still pretty new to Haskell, so I do not know where to start.

Solution

I was closer than I thought. See the answer for more details, but the solution was just to fix my syntax for the invocation of isPrefixOf (oops):

x ?^ q = fmap (x `isPrefixOf`) q

Solution

  • You gotta switch the arguments to isPrefixOf!

    x ?^ q = fmap (x `isPrefixOf`) q
    

    Other spellings are possible, such as this version that doesn't use the infix form of isPrefixOf:

    x ^? q = isPrefixOf x <$> q
    

    ((<$>) is another name for fmap.)