Search code examples
haskellxmonad

Select head of a list in Haskell


Now, I'm still learning haskell (to configure my xmonad wm), so bear with me.

I've written this function:

doesNameBeginWith :: String -> Query Bool
doesNameBeginWith str = fmap ( str `isPrefixOf`) (stringProperty "WM_NAME")  

Which will check the start of the WM_NAME X property against my str. I can compose it like this:

isMusic = doesNameBeginWith "MPD:" <||> doesNameBeginWith "ncmpcpp"

It works. Now I want to write a functions with this signature:

[String] -> Query Bool

so that I can call it like this:

isMusic = doesNameBeginWith ["MPD:", "ncmpcpp"]

The idea was to use foldr1 to parse the string list, but I can't figure out the sintax to get the head of the list... something like this:

doesNameBeginWith :: [String] -> Query Bool
doesNameBeginWith str = foldr1 (<||>) . fmap ( (head str) `isPrefixOf`) (stringProperty "WM_NAME") 

EDIT 1: as leftaroundabout suggested, I can combine two functions to get what I want:

doesNameBeginWithL :: [String] -> Query Bool
doesNameBeginWithL = foldr1 (<||>) . map doesNameBeginWith

But I'm still hoping for a more direct way and to understand how list heads could be used in this situation!

EDIT 2: Thank you all again for your answers, all of them have been quite informative! :)


Solution

  • If you already have a list, you don't need to parse it – you can simply deconstruct it.

    isThisStuff :: [String] -> Query Bool
    isThisStuff [c,d] = doesNameBeginWith c <||> doesNameBeginWith d
    

    Then

    isMusic = isThisStuff ["MPD:", "ncmpcpp"]
    

    Note that this fails if the supplied list doesn't have exactly two elements.

    Arguably, a fold-based solution is better. As you've noted, this can be written

    foldr1 (<||>) . map doesNameBeginWith
    

    Now, all you need to do to get rid of doesNameBeginWith in there is to inline it. Note that

    doesNameBeginWith = \str -> fmap ( str `isPrefixOf`) (stringProperty "WM_NAME")
    

    So you can use

    foldr1 (<||>) . map (\str -> fmap ( str `isPrefixOf`) (stringProperty "WM_NAME"))
    

    If you don't like the lambda, you can formulate this as a list comprehension:

    doesNameBeginWithAny strs = foldr1 (<||>)
         [fmap ( str `isPrefixOf`) (stringProperty "WM_NAME") | str <- strs]
    

    Alternatively, you can make doesNameBeginWith point-free

    doesNameBeginWith = \str -> fmap (isPrefixOf str) (stringProperty "WM_NAME")            
                      = \str -> flip fmap (stringProperty "WM_NAME") (isPrefixOf str)
                      = \str -> flip fmap (stringProperty "WM_NAME") . isPrefixOf $ str
                      = flip fmap (stringProperty "WM_NAME") . isPrefixOf
                      = (stringProperty "WM_NAME" `fmap`) . isPrefixOf
    

    so

    doesNameBeginWithAny = foldr1 (<||>)
                         . map ((stringProperty "WM_NAME" `fmap`) . isPrefixOf)
    

    However

    all of this is overly complicated. It gets much simpler when you split up the problem:

    1. Retrieve stringProperty "WM_NAME" in the Query monad.
    2. Match any of the supplied strings with it.

      doesNameBeginWithAny :: String -> Query Bool
      doesNameBeginWithAny prefs = do
         wmName <- stringProperty "WM_NAME"
         return $ any (`isPrefixOf`wmName) prefs
      

    This can also written with fmap, as Freerich Raabe showed.