Marc Claesen
Marc Claesen

Reputation: 17026

Idiomatic way to do conditional list comprehension

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:

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

Answers (2)

Erik Kaplun
Erik Kaplun

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

leftaroundabout
leftaroundabout

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

Related Questions