I want to focus on one point that I find an interesting study in language design:
> * I haven't found an expression that is better written as a list comprehension than as a map/reduce/select expression.
map/select are better iff you have a concise block syntax.
For mapping with a single ready function, all styles are reasonable:
items.map foo
items.map { |x| foo(x) }
map(foo, items)
[foo(i) for i in items]
Now suppose you want to modify the mapping function to foo(bar(i)). You could cascade it:
items.map(foo).map(bar)
map(foo, map(bar, items))
but this quickly gets out of hand and makes modifications painful. What you really want is a place to write arbitrary expressions instead of just a function name.
With comprehensions OR a good block syntax, it's a smooth transition:
[foo(bar(i)) for i in items)]
items.map { |i| foo(bar(i)) }
But without one, the change is ugly and annoying:
map(lambda i: foo(bar(i)), items)
So why wouldn't Python just embrace a nice block syntax?
Precisely because this naturally leads to expressing most control structures as functions taking blocks!
Now that is not wrong in itself, but it is a question of taste. Pythonic taste favors a different approach: instead of passing the block directly to a function, have a few built-in control structures that can act as combinators.
E.g. don't write an "each" method that accepts a block - write an iterator, and use the built-in "for" loop as a bridge between the iterator and the block. Why would this be any better?
1. Because we can: it turns out that the vast majority of custom control structures can be sorted into a few patterns (iteration, pre/post guards, function wrapping), each served by one natural combinator ("for", "with", decorators). There is a price: it takes time until new patterns are recognized and the missing combinator is added (e.g. "with" is a very recent addition).
2. Decoupling: an iterator is a self-contained passive object that can be used outside of a for loop. An "each" method takes control and is less versatile.
3. Readability: seeing the combinator gives you an immediate idea about the style of flow control. This is just a matter of taste (Lisp and Ruby solve this with naming conventions).
4. Syntax taste: stupid as it sounds, passing blocks to functions is just alien to Python's syntax. Many smart people tried to marry them - and it wasn't a pretty sight...
But in the end you can't judge decisions of taste by pro/con arguments. You have to look at the sweet spots (and the sour spots) that arise in practice.
A particularly nice sweet spot that arose in Python is the combination of reduction functions with generator expressions:
sum(i**2 for i in range(10))
any(p % i == 0 for i in range(2, p - 1))
dict((v, k) for (k, v) in dict_to_invert)
This is very cool because the map-reduce pattern covers lots of useful computations. Of course it's just a one case and YMMV. I'm sure Ruby has its own sweet spots...
> * I haven't found an expression that is better written as a list comprehension than as a map/reduce/select expression.
map/select are better iff you have a concise block syntax.
For mapping with a single ready function, all styles are reasonable:
Now suppose you want to modify the mapping function to foo(bar(i)). You could cascade it: but this quickly gets out of hand and makes modifications painful. What you really want is a place to write arbitrary expressions instead of just a function name.With comprehensions OR a good block syntax, it's a smooth transition:
But without one, the change is ugly and annoying: So why wouldn't Python just embrace a nice block syntax? Precisely because this naturally leads to expressing most control structures as functions taking blocks!Now that is not wrong in itself, but it is a question of taste. Pythonic taste favors a different approach: instead of passing the block directly to a function, have a few built-in control structures that can act as combinators.
E.g. don't write an "each" method that accepts a block - write an iterator, and use the built-in "for" loop as a bridge between the iterator and the block. Why would this be any better?
1. Because we can: it turns out that the vast majority of custom control structures can be sorted into a few patterns (iteration, pre/post guards, function wrapping), each served by one natural combinator ("for", "with", decorators). There is a price: it takes time until new patterns are recognized and the missing combinator is added (e.g. "with" is a very recent addition).
2. Decoupling: an iterator is a self-contained passive object that can be used outside of a for loop. An "each" method takes control and is less versatile.
3. Readability: seeing the combinator gives you an immediate idea about the style of flow control. This is just a matter of taste (Lisp and Ruby solve this with naming conventions).
4. Syntax taste: stupid as it sounds, passing blocks to functions is just alien to Python's syntax. Many smart people tried to marry them - and it wasn't a pretty sight...
But in the end you can't judge decisions of taste by pro/con arguments. You have to look at the sweet spots (and the sour spots) that arise in practice. A particularly nice sweet spot that arose in Python is the combination of reduction functions with generator expressions:
This is very cool because the map-reduce pattern covers lots of useful computations. Of course it's just a one case and YMMV. I'm sure Ruby has its own sweet spots...