You're not wrong, list comprehension syntax isn't needed. But neither is do
notation (which BTW desugars in almost the same way), or simple list literals, or even string literals. Instead of writing "Hello, World!"
you could always write
'H':'e':'l':'l':'o':',':' ':'W':'o':'r':'l':'d':'!':[]
Going a step further you could also ask why we don't just write programs in lambda calculus / System F, instead of an actual programming language.
You can probably see where I'm getting with this:
Why would you ever use list comprehension
– because they're concise, nice-looking and it's easily understandable what's meant.
Specifically, I think list comprehensions nicely put the information that's most useful for quick understanding up-front: for example, [x*2 | x<-[1..5]]
is parsed by the eye in something like
[...
ok, there's a list coming up
[x*2 ...
ok, at least one entry has the form x*2
(whatever x
is), i.e. an even number.
[x*2 |...
ah, all entries are even numbers, quantized over some source
[x*2 | x<-...
ok, so x
is what changes for each entry of the list
[x*2 | x<-[1..5]
right, so the specific numbers used will be [1..5]
This is, I'd say, indeed in quite many situations roughly the order from most- to least interesting for a quick read. It basically starts by explaining by example (or, prototype) what kind of result entries you get, before getting into the details of how the entries differ.
The extreme opposite is the imperative-style
forM [1..5] $ \x -> do
return $ x*2
Here, we begin with "start a loop" and "loop over those numbers", which is nice in the sense that it gives an overview about what's about to happen operatively, but little insight regarding the denotational meaning of the result. It then goes on to define the variable x
, etc..
The map
version is somewhere in between, with the extra quirk of using point-free syntax. Which has its own advantages and disadvantages.
All three styles have their use cases, it's up to judgement which one works best in each situation.