Hacker News new | ask | show | jobs
by frig 6036 days ago
It depends slightly on how you came to functional programming.

You can arguably trace Python's list comprehension syntax all the way back to setl, a "set-theoretic programming language", and the resemblance to mathematical set-builder notation is intentional; compare

- let B = { g(a) s.t. | a \in A and f(a) holds}

- B = [g(a) for a in A if f(a)]

If you're used to thinking in sets having to decompose into "maps" and "filters" is a speedbump; easy to do but nice to avoid.

Where list comprehensions really start to shine is making it comparatively trivial to pull from multiple source collections without a lot of ugly machinery:

    [{'widget':w,'sprocket':s,'location':l} for w in widgets for s in sprockets for l in locations if l.hasInStock(w) and l.hasInStock(s) and w.isUsableWith(s)]
...which is about where explicit map + filter start to become annoying. You can use:

    map(lambda i: {'widget':i[0], 'sprocket':i[1], 'location':i[2]}, filter(lambda i: i[2].hasInStock(i[0]) and i[2].hasInStock(i[1]) and i[0].isUsableWith(i[1]), itertools.product(widgets,sprockets,locations)))
...but to my eyes that is not only very ugly but just going by character count the # of characters given over to keywords (map, lambda, filter) instead of "what i'm doing here" is huge. Additionally use of itertools forces use of tuples for your intermediate values and thus the lambdas are gobbledegook until you get to the tail end of the statement and see that i[0] == widget, i[1] == sprocket, and i[2] == location. I could define some constants (WIDGET = 0, SPROCKET = 1, LOCATION = 2, etc) but now it's even longer.

It gets even worse if you try to be clever with the sequence of operations.

You might look at that definition and say lo! I can pre-filter out stuff not in stock at each location and make things more efficient. Naively you'd wind up with:

  map(lambda d: {'location':d[0], 'widget':d[1], 'sprocket':d[2]}, flatten(map(lambda l: list(filter(lambda p: p[1].isUsableWith(p[2]), itertools.product([l],filter(lambda w: l.hasInStock(w), widgets), filter(lambda s: l.hasInStock(s), sprockets)))), locations))) #nb must-supply-you-own-flatten-method
Under some circumstances that might be substantially faster than the previous approach. But compare the equivalent but you could have instead gone with:

    [{'widget':w,'sprocket':s,'location':l} for l in locations for w in [widget for widget in widgets if l.hasInStock(widget)] for s in [sprocket for sprocket in sprockets if l.hasInStock(sprocket)] if w.isUsableWith(s)]
So you lose a little flexibility in simple cases at in exchange for increasing the scope of what you can get away with as "readable" one-liners.
1 comments

Python's lambda sucks, but that does not necessarily mean map/filter sucks with it too. Of course there are cases where list comprehensions are more convenient and more powerful (esp. if they are more like their "real" counterparts in Haskell and friends). But in cases when map/filter are more convenient, I like the option of using them.
I'm partially defending list comprehensions here and partially challenging you to consider the possibility that there are higher levels of abstraction out there than those employed by functional programming primitives like map and filter.

Level 0: for-loops with an explicit accumulator

Level 1: map + filter (!)

Level 2: ??? arguably an atemporal set-theoretic approach

In practice in python list comprehensions are a superior syntax for computing with multiple source collections.

(!) Really all you need is reduce

    map = lambda f,l: reduce(lambda h,t: h + f(t), l, [])
    filter = lambda f,l: reduce(lambda h,t: h + t if f(t) else h, l, [])
You'd be silly to implement them that way of course but know your tools.
[NOTE: copied from another branch for easy reading for others]

A saner map/filter/product version:

    map(lambda (w,s,l): {'widget': w, 'sprocket': s, 'location': l}
        filter(lambda (w,s,l): l.hasInStock(w) and l.hasInStock(s) and w.isUsableWith(s), 
            product(widgets, sprockets, locations)))