Reputation: 65
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! :)
Upvotes: 0
Views: 2210
Reputation: 120751
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)
all of this is overly complicated. It gets much simpler when you split up the problem:
stringProperty "WM_NAME"
in the Query
monad.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.
Upvotes: 1
Reputation: 94499
You just need to change the function you're mapping over the the string property a bit; the any
function comes in handy. Instead of
doesNameBeginWith str = fmap ( str `isPrefixOf`) (stringProperty "WM_NAME")
try
doesNameBeginWith xs = fmap (\x -> any (`isPrefixOf` x) xs) (stringProperty "WM_NAME")
Upvotes: 2
Reputation: 15290
As you realised, head
is how you can get the first element of the list, but the solution I'd teach to newbies is to use pattern-matching and recursion to access list elements. Using your first definition of doesNameBeginWith
:
doesNameBeginAny :: [String] -> Query Bool
doesNameBeginAny (x:xs) = doesNameBeginWith x <||> doesNameBeginAny xs
doesNameBeginAny [] = return False
After the type declaration, there are two cases. One case takes a list with at least one item (x
), and calls your first function on that one item, combining the result with a recursive call on the rest of the list (xs
, which may be empty). The pattern matching is responsible for taking apart the list to get at the first item each time, so you don't need to call head
. The second case handles the empty list.
As you've already found, the function foldr1
(as well as the other folds) abstracts away this structure of the function. You can use a map to transform the list into a list of Query Bool
, and then a fold to reduce the list to a single item by or'ing the elements together.
doesNameBeginAny xs = foldr (<||>) False queries
where queries = map doesNameBeginWith xs
I've used where
to define an intermediate to make the code easier to follow. I've also used foldr
rather than foldr1
so the function also works on empty lists. Again, you don't need to call head
because map
and fold
are responsible for taking apart the list.
If you're looking for an answer that pulls apart the list explicitly, more like a for-each loop in Python or Java or some other imperative language, that's just not how you write Haskell.
Upvotes: 1