Reputation: 17026
I just started learning Haskell and I can't seem to find a good solution to create a list conditionally.
Basically, what I want is to do list comprehension with an if/else
but without the else
part. I am certain this is possible, I guess I am just using the wrong keywords on my googling quest.
A very stupid example in Python which I want to Haskellize:
[x for x in range(11) if x > 5]
In Haskell, as far as I understand we cannot omit else
blocks like I did in the Python example. How should I do something like this? Does something like nothing exists which I can add to the else
-block in list comprehension, like so:
[if x > 5 then x else Nothing | x <- [0..10]]
I actually came across Nothing in Haskell, though I haven't figure it out yet. It certainly doesn't seem to do what I hoped. Basically I don't want an else
in my list comprehension, but if it's a necessary evil I want to insert nothing in the else
block.
I can think of a bunch of hacks to get similar functionality very inefficiently:
filter
the list after creating it, e.g. filter (>5) [0..10]
concat
them, e.g. concat [if x > 5 then [x] else [] | x <- [0..10]]
These ideas all seem really ugly, though.
Ofcourse in practice I don't want to create conditional lists with such trivial conditions.
Upvotes: 14
Views: 16166
Reputation: 38227
Use this:
Prelude> [x | x <- [0..10], x > 5]
[6,7,8,9,10]
In Haskell list comprehensions, the "source" expression and all the filters/ifs are "siblings", i.e. there's not much syntactic distinction between them, unlike in Python. So
<expr1> for <source_expr> if <cond_expr>
is just this in Haskell:
[<expr1> | <source_expr>, <cond_expr>, ...]
(source_expr
being x in range(0, 10)
in Python or x <- [0..9]
in Haskell)
and you can have as many "source" and "filter" expressions in a Haskell list comprehension as you like.
This also means that you can write stuff in a style more similar to the mathematical notation; consider:
{ x : x ∈ [0, 10), x > 5 }
and see how the Haskell version is almost the same, compared to the Python one, which looks much more procedural/imperative.
This also works out trivially without any need for additional syntax/constructs with multiple "source" expressions:
Prelude> [(x, y) | x <- [0..10], y <- [10..20], y - x < 5]
[(6,10),(7,10),(7,11),(8,10),(8,11),(8,12),(9,10),(9,11),(9,12),(9,13),(10,10),(10,11),(10,12),(10,13),(10,14)]
In Python you would have to have what looks like a nested list comprehension, however Haskell still just sticks to the mathematical approach/notation.
Upvotes: 28
Reputation: 120731
What Python calls if
doesn't exist as such in Haskell. For "if without else" to make sense at all you need a notion of what "doing nothing" even means – normally, the very idea doesn't make sense in a functional language because functions are all about I guarantee you, for any argument you give me I'll return a useful answer. You can't just say, "nah, don't feel like returning anything"... unless no-result happens to be a value of the return type. That is in fact given for lists, which is the only reason it's useful to think about filtering in the first place.
More generally, "nothingness" is captured by a particular kind of monad: the MonadPlus
class. In fact, the simplest instance is the Nothing
constructor you've already stumbled on:
Prelude Control.Monad> mzero :: Maybe Int
Nothing
You see it has Maybe
in its type, i.e. we make it explicit that it's not guaranteed there will be a result!
Selecting between "yield something, or not" is not called if
in Haskell, but guard
. In monadic writing, your comprehension looks thus:
yourList = do
x <- [0..11]
guard (x > 5)
return x
And indeed the list comprehension [ x | x <- [0..11], x>5 ]
is basically syntactic sugar for that.
guard
is a bit weird if you don't understand monads. There's another function you'll also see often which does something very similar (albeit in quite a different way), when
, this one is normally used for the "execute-or-don't–if" in imperative languages.
Upvotes: 5