The semantic difference between for and for

20th May 2018

In Python we have for loops and list comprehension. In this post we'll examine the semantic difference between the two in order to determine when it is appropriate to use each one.


IN THE beginning there was GOTO, but that was a bit basic. Then came the for (i=1; i< 10; i++) or in Python-land for i in range(10):. But that wasn’t encoding our actual intention, only the procedure. So now we have our “pythonic” for loop.

In this post, we’ll use loops to greet some people using this list and function.

names = ["Cepheus", "Cassiopeia", "Andromeda"]

def greet(name):
    print("Hello, {}!".format(name))

The for Loop

THIS SIMPLY means, “for everything in the list, we want to do something”. I know, it’s no great surprise, but this is the use case for the for loop. It is the intention of the author of the for loop.

for name in names:
    greet(name)

List Comprehension

THIS IS an idea taken from set theory, known as set-builder notation, where you might see something like this.

\{ x | x \in A \land P(x) \}

This can be read as “x for x in A if P(a)” where P is some predicate. In Python we could write:

[greet(name) for name in names]

This would produce the same output as the for loop, but we shouldn’t write that.

If you look at what this returns you’ll find a list: [None, None, None], because this is list comprehension and the function greet returns None. The meaning of this syntax is “make me a list of values”, which is not the same as a for loop.

This semantic meaning would be valid for this example, which is making a list of values:

upper_names = [name.upper() for name in names]
Using a list comprehension to make a list of values encodes your intention, using it as a one line for loop does not.

But, One Line!

I UNDERSTAND using list comprehension when you don’t want the list, sometimes you just want a nice, simple, one liner. Good news, there’s map for that:

map(greet, names)

Except in Python, that’s a generator, so wont actually evaluate. Semantically, when mapping a function over a collection in this manner, it is equivalent to list-comprehension, because you have to wrap it to actually make it execute, and that returns a list.

list(map(greet, names))

This annoys me so much, and so frequently, I write my own map function for use in my code, along with parallel and concurrent versions.

def mapped(*args):
    list(map(*args))

mapped(greet, names)

Conclusion

SIMPLY PUT, for loops mean “do something for each thing in a collection”, whereas list comprehension means “make me a list”. Use them appropriately to encode your intention.

I’ve not covered the generator version of the list comprehension syntax because the argument about semantics is very similar. If you’d like to investigate map further, I’ve got a post where I refactor some imperative code to use functional iteration here.

Post Tags


All Tags



LIFE IS better when we share.


Comments

JOIN THE conversation: awesome comments.