Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The fact that he proposed removing map, reduce, filter, and lambda from Python 3.

In the case of map and filter, is there any compelling reason to use either of those instead of list comprehensions or generator expressions?



A single, non-nested list comprehension or generator exp is basically map(filter). You need nesting to get filter(map).

e.g.

    map(expensive_call, filter(cond, seq))
equals

    [expensive_call(each) for each in seq if cond(each)]
but

    filter(cond, map(expensive_call, seq))
equals

    [each for each in [expensive_call(x) for x in seq] if cond(each)]
note because of "expensive_call", it's inefficient (and silly) to do

    [expensive_call(each) for each in seq if cond(expensive_call(each))]

So map/filter combination gives more flexibility than list comprehension, and for functional-thinking minds, it's just so natural to think in abstract terms of passing functions around. List comprehension is pretty syntactical sugar to do similar things, but it forces you think about the "how to do" instead of "what to do".

That said, it's not really "compelling" though -- there is no real "compelling" reason to switch from one Turing-complete language to another given that you can do the same thing eventually. But hey, it's the itches that drive us nuts, isn't it? :)

Just my 2 cents


I don't get how map/filter is more flexible than list comprehensions. Map/filter require nesting:

    (filter (map expensive_call X) cond)
So do list comprehensions:

    [y for y in [expensive_call(x) for x in X] if cond(y)]
Near as I can tell, the only difference is that list comprehensions also provide a syntactic sugar for the convenience function filter_then_map. Sometimes this saves you a level of nesting, sometimes not.

Incidentally, this isn't even an issue in a pure functional language with sufficiently smart compiler.


Well, FP is mostly about nesting, but it's uniform and people get used to thinking that way. List comprehensions, on the other hand, are more like procedure code (conceptually), and it sucks to nest them. Nesting aside, it's the different level of abstraction that matters for FP-ers.

Plus, you get 5 mentions (3 y's and 2 x's) of some intermediate variables instead of 0 in your code, so both token-wise and char-wise, map/filter alternative is shorter, and less a mental burden (think about the "succinct" idea by PG).


I have a longer comment here that argues the opposite: list-comprehensions are only a syntactic pun or two away from set-builder notation, which is a higher level-of-abstraction (it declaratively states what it is) than map+filter (which specify a procedure to generate it, albeit at higher level of abstraction than a for-loop).

If you can put together a nontrivial usage of map+filter with at least three source collections that's more concise than the equivalent list comprehension I'll (figuratively) eat my hat.


I'm not so sure if set notation is higher level, but for the example in your longer comment, map/filter/product is not that bad if you use it wisely. Here is my 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)))

Well, I agree it is not any conciser that its list comprehension (about the same I guess?). Nothing is perfect, like you said, know you tool :)


You'll be thrilled (lol) to know that the lambda-tuple syntax isn't in python 3 (!); it does make my examples more concise.

The argument in favor of set-notation being higher level is it's less specific (it doesn't explicitly provide a sequence of operations, just an outcome).

List comprehensions look like set notation but have an implicit procedural translation you have to keep in mind to use them well, so it's a toss-up.

I prefer map/filter/reduce when sequencing has large performance implications but for simple filtering or raw-data-shaping comprehensions read more smoothly.

http://books.google.com/books?id=RvY5BM0Xt1wC&lpg=PT367&...


> You'll be thrilled (lol) to know that the lambda-tuple syntax isn't in python 3

Now you know why many people like me are gradually pissed off by Python and start the exile ... currently trying Scala and it seems a nice language (with list comprehension too! :)

> List comprehensions look like set notation but have an implicit procedural translation you have to keep in mind to use them well, so it's a toss-up.

Actually I think that's the problem I have with list comprehensions: I use them a lot in my code, usually 1~3 levels nested and then have a hard time tracking down the order of implicit loops (which is inner vs outer) and make sure the intermediate variables (x for x in y for y in z ...) do not clash ... OK maybe I'm using it too much and in the wrong way :(

> I prefer map/filter/reduce when sequencing has large performance implications but for simple filtering or raw-data-shaping comprehensions read more smoothly.

I didn't know map/filter is faster than list comprehensions? I thought both are optimized by Python interpreter. But I like the idea of knowing that at least map can be parallelized easily. But since Python does not utilize multicore in a decent way, all bets are off :(


Using do-notation is probably cheating, but what do you think of this? (in Haskell):

  do { w <- widgets;
       s <- sprockets;
       l <- locations;
       guard (l `hasInStock` w);
       guard (l `hasInStock` s);
       guard (w `isUsableWith` s);
       return (w, s, l); }
We're trading horizontal space for vertical space. I think it's much clearer than either list comprehensions or plain map/filters. It's the best of both worlds.


I like the look of it. Am I correct that the order the statements are in translates into the order things are evaluated in when the code is called?

If eg I edited it to be:

    do { l <- locations;
         w <- widgets;
         guard (l `hasInStock` w);
         s <- sprockets;
         guard (l `hasInStock` s);
         guard (w `isUsableWith` s);
         return (w,s,l); }
Does that force it to go through "location-first" and only check the w and s of (w,s,l) for compatibility if it has already ascertained that w and s are in stock @ l?


Yes, that's correct.


List comprehensions are not conceptually like procedural code at all. They are completely declarative.

List comprehensions: the set contains this.

Map/filter: apply these functions to the set.

Procedural: follow this recipe for turning one list into another.


Maybe it's just me ... Every time I see something like

    [fn(x) for x in xs for xs in xss if cond(xs)]
I always think it's a loop, then an inner loop (and loops -> procedure ... then get confused which is inner and which is outer :|


Most times you are interested in doing the simple, e.g.:

  filtered = [x for x in seq if x>10]
Python's list comprehension is much more readable than using map/filter/reduce - at least for Python programmers :) Anyhow, I really like Guido's decision on dropping these - it creates a cleaner language and forces people to think Pythonic when programming in Python.


> Python's list comprehension is much more readable than using map/filter/reduce - at least for Python programmers

For simple cases, yes. But I wouldn't say

    [each for each in [expensive_call(x) for x in seq] if cond(each)]
is more readable than

    filter(cond, map(expensive_call, seq))
at least for functional-thinking minds. The level of thinking in abstract is different here.

Now the problem is, some people see Python as a very functional language (with first-class functions etc) and want to use it that way (like Lisp), but BDFL and some core Python devs believe it is better to keep it Pythonic, thus those functional people are kinda pissed off by this and switch away from Python.

Personally I don't think it will make Python a lot cleaner to remove two auxiliary functions and force people to use list comprehension when it is completely trivial to add these missing pair back (two lines of code).

(disclosure: I prefer FP, but I also think keeping things Pythonic is fine most of the time. It's just that in this case, I think map/filter is pretty "Pythonic" according to me. :)


At first, I thought "wow, Python generator expressions are really ugly nested". This is especially true after working with C#3/Linq because query expressions have natural places for line breaks and read in a more consistent order.

Later, I ran into some cases where I wanted a multi-line lambda. And my thought was "aaragh134!#?!"

Then, a weird thing happened. I started making a conscious effort to follow PEP 8. 79 column limit? Seriously? That sucks. But after weeks of struggling with it, something finally hit me. I realized I was writing better code by forcing myself to reduce code density. Sure, it was a little bit longer, but I spend way more time reading it than writing it.

Stop trying to fight it; assign a name to that lambda. Readability counts.

Stop trying to be clever; assign a name to that inner expression. Flat is better than nested.


I know you all know this, but I feel like it bears mentioning that nobody is forcing you to use list comprehensions whether map/filter stay in Python's built-ins or not. They can be defined in around 3-4 lines of code each. Lisp aficionados, already accustomed to the bottom-up style of programming, ought to have no problem writing functions like these as necessary.


There's a difference between things being possible and being encouraged.

De-emphasizing functional operations makes it more likely for libraries to work in a non-functional style, for tutorials to do so, etc.

It's tiring fighting against a community and a (benevolent) dictator that disagree with you.


> It's tiring fighting against a community and a (benevolent) dictator that disagree with you.

That summarizes the problem I guess :D


If I remember correctly, "removing" these functions mostly just meant dumping them into the functools module rather than including them as a built in function.


Yeah I know. Maybe "force" is not the right word ... probably "discourage"?

Actually only one line of code is enough for each of map/filter:

    def map(fn, seq): return [fn(each) for each in seq]
    def filter(cond: seq): return [each for each in seq if cond(each)] 
But seriously, what do you really gain by removing these two? Isn't that too ideological? I don't really see how un-Pythonic it would be to use map/filter instead of list comprehensions. The problem is BDFL's attitude seems to drive many FP-ers away, like the guy in the original post.


They ultimately weren't removed. reduce was removed from the builtins, but it's still just one import away. From itertools import reduce.


Um...

    (filter #(> x 10) seq)
sure is readable for me, and I'm fluent in Python and various Lisps. (That example is Clojure.)

I would like to point out, however, that CL allows you to write:

    (loop for x in seq
          when (> x 10)
          collect x)
which you might think is verbose ("why do I need that 'collect'?")... except that loop allows you to write things like

    (loop for i in *random*
          counting (evenp i) into evens
          counting (oddp i) into odds
          summing i into total
          maximizing i into max
          minimizing i into min
          finally (return (list min max total evens odds)))
Loop knocks Python's trivial list comprehensions into a cocked hat.

I switch between map/filter and loop depending on whether I'm working with predefined functions (e.g., (filter 'less-than-ten seq)), handling multiple sequences, doing side-effects, etc.


>Loop knocks Python's trivial list comprehensions into a cocked hat.

CL could really use something like Python's generators though. The loop macro is an ugly hack in comparison.


[It] creates a cleaner language and forces people to think Pythonic when programming in Python.

The question is whether "Pythonic," as the community defines it today, is optimal in all cases. As an analogue, there have been enough talks about things the Java community considers to be stylistically optimal that look really horrible compared to implementations in other languages -- like Python. :-) Pythonic style should be a guide, not an edict, and should be deviated from or redefined when it makes sense. The examples riobard provided already show the syntax weighing things down, a situation where syntax should give way to a more functional style approach.

As for it creating a cleaner language, I try to approach this, as with all things, from the perspective of being an language-agnostic programmer. From that perspective, I do like the python syntax for simple things like what you defined, but under heavier weight mapping and filtering operations, the map/filter function call syntax seems a lot cleaner. There's nothing wrong with syntactic sugar, but I would assert that it will tend to suck when it is all you have.


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.


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)))


Nope - just a style thing. You don't need lambdas either - named functions are just as powerful.

But I often think 'functionally.' Since I have options, I'd rather go with a language that allows me to program how I think - instead of being forced to translate my thought into its semantics.


Nope - just a style thing. You don't need lambdas either - named functions are just as powerful.

It depends on what you define as power, and how wide of a continuum you are willing to presume that it runs.

Named functions add two levels of clutter.

The first is to the actual code, because you have to add a name to something that never wanted one, and it has to be defined apart from where it is used. You could give it a fluff name, but that is worse than no name at all. The reader is left wondering what the function is for, which requires carrying unnecessary mental baggage. It would seem more powerful to me that you just define a function where it is used, and not worry about having to carry around that extra mental information for later on; the less state you need to keep the better. But I may only say that because I, like you, tend to think functionally.

The second is to the namespace, because now there is a symbol in the environment that doesn't need to be there. It's not a huge deal, but it is just another bit of extraneous fluff.

Anyway, not meaning to turn this into a language war. Just a couple of thoughts that popped into my head when I read your first paragraph.


I agree with this big time. I hate increasing the "vocabulary" of my program unnecessarily.


The "correct" alternative to multiline lambda might be not a named function but an explicit code block: no clutter at the prize of vertical space.

  result = []  
  for item in iterable:
      # here goes multiline lambda as an explicit suite
      # that computes value
      result.append(value)


Obviously, map and filter are better because ... ah, hmm, I don't know.

I think it is a matter of language design. Higher order functions are not as prominent in Python as say, Haskell, where map & friends are generally preferred for composability. Lispers prefer map & friends just because list comprehensions add all that messy syntax.

If I were a Python programmer, I would probably use list comprehensions, since they seem to be the preferred idiom.


map, filter and reduce were not removed from python3.x, however. They'll be there for the next decade at least.


I like lambda because it allows anonymous functions-- functions that can be defined on the fly (dynamically) and then thrown away. IMO, it's generally bad form for a program to be dynamically creating named functions, filling the namespace.

Also, I like map and filter because they aren't just functions in the procedural sense but combinators: you can pass them around, using them as arguments and returning them. It's much harder to pass around a syntactic entity like a list comprehension (although I'm glad list comprehensions exist; as shorthand they are great in source code.)

All this said, I haven't used Python in 3 years, in favor of purely functional languages such as Clojure and ML, so I might be way out of date.


You bring up a good point about passing map and filter around. I hadn't really thought of that.

I still prefer to user list comprehensions, and don't think it would be so bad to have map, filter, reduce live in the itertools module. This would stylistically match what is done with the comparison operator functions living in their own module, which you can import if you need to pass the functions around.


List comprehensions are great, but they're sort of a DSL-- something the syntax recognizes as special and converts to something else. It's unusual that you can pass around higher-order syntactic elements, while every modern language worth its salt allows you to pass around functions.

Common Lisp has something similar, called LOOP. Implemented as a macro, it's a within-Lisp DSL for expressing looping constructs, e.g.

(loop for i from 1 to 10 sum i) => 55

(loop for c across "bar" collect c) => (#\b #\a #\r)

It's controversial within the CL community, because the loop language looks much more like traditional languages than Lisp.


Indeed, we figured out recently that it's basically embedded Algol 68... which seems superbly fitting somehow.


Common Lisp is very, very old. Tagbody and progv, anyone?

It's an influential language, for sure, but I prefer Clojure for a number of reasons.


The automatic distaste in our industry for things that are old is a disease, a form of vanity. Old ideas are not inadequate because they are old. Most great ideas are old. Sometimes, of course, an old thing is a vestige of some ancient limitation that no longer applies. Those ones are good to clear away. But the prevailing thought process in the software industry, no less dominant for its astonishing primitivity, is to reject the old per se. The will to novelty is so extreme that it doesn't matter if the new thing is worse, only that it is newer. We want to program in new languages like we want to drive new cars (a bad analogy in Common Lisp's case, unless you assume that the older car is both faster and more fuel efficient). Lisp in general, and Common Lisp in particular, is up against this dynamic. You can't understand the reactions to it without accounting for that.

That CL contains TAGBODY is a brilliant thing. It amazes me that a language can be at once so high-level and so low-level. I haven't had occasion to use it yet, but the fact that it's there, and that the higher-level abstractions are built in terms of it, is a thing of beauty to me. (I don't know about PROGV.)


You make a great point, and I think Lisp has enough fundamental value to deserve its place as the "100-year language". In 2060, people will still be using some descendant of Lisp. On the other hand, I think some aspects of CL are outmoded. I don't like the lack of support for maps as a top-level structure, and equality in CL is seriously broken, IMO.

I'd prefer to code in CL over Java/Blub, but I prefer Clojure over CL, and probably Haskell or ML over Clojure. (Lisp has better syntax, and macros are very cool, but static typing wins for large projects, in my opinion.)


IMO, it's generally bad form for a program to be dynamically creating named functions, filling the namespace.

That's what inner functions are for. This isn't the best example, but you get the idea:

  def foo(mylist):
    def n_to_the_n(n):
      if n > 1:
        return n**n
      return 1
    return [n_to_the_n(i) for i in mylist]


Do the inner functions get cleaned up after you exit the function body?

I'm used to Lisps, where def/defun/define/defn imply internment. For example, in Clojure:

  (defn foo [list]
    (defn n-to-nth [n] (Math/pow n n))
    (map n-to-nth list))
Would intern the symbol 'n-to-nth in the current namespace. So every time I see "def", I think of something that has that (mild, almost always innocuous) side effect.


The inner function just falls out of scope, and is presumably collected.

  >>> def foo(mylist):
  ...   def n_to_the_n(n):
  ...     if n > 1:
  ...       return n**n
  ...     return 1
  ...   return [n_to_the_n(i) for i in mylist]
  ... 
  >>> print foo([1,2,3,4,5])
  [1, 4, 27, 256, 3125]
  >>> foo
  <function foo at 0x6e5f0>
  >>> n_to_the_n
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  NameError: name 'n_to_the_n' is not defined
  >>>
Note that I only made the inner function longer than necessary for illustrative purposes :-)


>I'm used to Lisps, where def/defun/define/defn imply internment. For example, in Clojure:

With regard to 'define', it doesn't work like that in Scheme. A define in a function body behaves just like a let.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: