How to evaluate `(any (isSpace . (: [])) "abc"`

I originally wrote the below which worked

isSpace :: [Char] -> Bool
isSpace str = str /= " "

isJustSpaces :: [Char] -> Bool
isJustSpaces str = length (filter isIt (map (:[]) str)) == 0

Maybe not so bad for a JS guy :slight_smile:

hlint helped me write

isJustSpaces :: [Char] -> Bool
isJustSpaces str = not (any (isSpace . (: [])) str)

It is used in the context of

keptQuiet :: [Char] -> Bool
keptQuiet str = isJustSpaces str || str == ""

keptQuiet "" -- True
keptQuiet " " -- True
keptQuiet "Hi " -- False

What I’m not sure of is howisJustSpaces is evaluated.

Is "abc" being created and then passed to isSpace as in

= not (any (isSpace . "abc")

Or is it sending isSpace one character at a time? If so, what is causing that to happen?

The “abc” is substituted as the str argument:

isJustSpaces str = not (any (isSpace . (: [])) str)
isJustSpaces "abc" = not (any (isSpace . (: [])) "abc")

So “abc” becomes the second argument to any. any has this type:

any :: (a -> Bool) -> [a] -> Bool

Since any accepts a list, you can think of “abc” as a list of characters here. Then the function argument to any - (isSpace . (: [])) - must be a predicate on characters - Char -> Bool.

So in this case the type of any is specialised to

any :: (Char -> Bool) -> [Char] -> Bool

So you’re right that it’s sending the characters to that function one at a time. This is due to the definition of any, which checks the predicate for each element of the list it’s given.

Since (isSpace . (: [])) is a composition of two functions, each character is first sent to (: []), then the output of that goes to isSpace. (: []) is a function which puts an item in a singleton list, so it here has the type Char -> [Char] aka Char -> String. So it converts each character to a single character string. Then that string is passed to the isSpace function you defined, which returns a Bool. If any finds that (isSpace . (: [])) is true for any of the characters in the string, it returns True, otherwise it returns False. Then that result is finally negated by not.

This definition is slightly overcomplicated for a few reasons. The first is that there’s no need to convert a character to a string to check whether it’s a single space:

isSpace :: Char -> Bool
isSpace char = char /= ' '

I’m also only noticing just now that this actually does the opposite of checking whether it is a space haha. I think you meant (==) rather than (/=)

isSpace :: Char -> Bool
isSpace char = char == ' '

This allows us to eliminate the function which converts a char to a singleton string. We’ll also have to add a not composed with isSpace since we changed (/=) to (==).

Now we have:

isJustSpaces :: [Char] -> Bool
isJustSpaces str = not (any (not . isSpace) str)

This can be simplified further by noticing that using any here is a bit circuitous. What we actually want is for a condition to be true of all elements of a list, and that function exists.

isJustSpaces :: [Char] -> Bool
isJustSpaces str = all isSpace str

If you want to, you can make this point free:

isJustSpaces :: [Char] -> Bool
isJustSpaces = all isSpace

At this point you might even consider not defining isJustSpaces at all. Since its definition is so simple, it might actually make the code clearer to just inline the definition wherever it is called.

The last thing is that a function like your isSpace already exists in Data.Char, so you don’t need to define it yourself. The difference is that it works on other forms of whitespace as well, like tabs and newlines.

import Data.Char (isSpace)

Than you for you excellent and detailed analysis. It is really helpful!

I’m happy I asked for help. I knew I needed some but apparently more than I knew.

From reading your post I now see a few things

The type of (: []) "abc" is [[Char]] which was clearly not my intent and indicates I should be paying more attention to types.

It is also clear now that I didn’t need to do (: []) "abc" since any (=='a') "abc" -- True works.

It is also helpful that you pointed out that iterating over a String yields Char. I read that many times but it didn’t come to mind when trying to think things through.


You’re welcome, glad I could help!