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

> Python, being a multi-paradigm language with the strong functional side

Coming back to that, just a reminder that lambdas in Python are still gimped, closures do not work as expected because of -- scoping, and core developers in Python 3 tried to remove with "map" and "filter" tools that are considered quite essential for functional programming.



I actually wish they had done so.

As someone who switches between Python and functional languages, I find Python's "map" and "filter" to be a trap, and have taken to scrupulously avoiding them. The problem is that I expect those functions to be pure, and, in Python, they aren't. They actually can't be, not even in principle, because their domain and range include a core datatype that cannot be interacted with in a pure manner: generators. A generator will change its own state every time you touch it. For example:

  >>> seq = (x for x in range(1, 11))
  >>> list(filter(lambda x: x % 2 == 0, seq))
  [2, 4, 6, 8, 10]
  >>> list(filter(lambda x: x % 2 == 1, seq))
  []
In a language that is a good fit for functional programming, the last statement would return [1, 3, 5, 7, 9], not an empty list. But Python is imperative to the core, so much so that I would argue that trying to use it as a functional language is like trying to drive screws with. . . not even a hammer. A staple gun, maybe?

(Which isn't to say that you can't successfully use some functional techniques in Python. But it's best done in a measured, pragmatic way.)


A good example why immutability by default seems to be the right thing - in Clojure, "seq" would not have been modified by the first filter expression:

    user=> (def seq_ (range 1 11))
    user=> (filter (fn [x] (== (mod x 2) 0)) seq_)
    (2 4 6 8 10)
    user=> (filter (fn [x] (== (mod x 1) 0)) seq_)
    (1 2 3 4 5 6 7 8 9 10)
or more concisely:

    user=> (filter even? seq_)
    (2 4 6 8 10)
    user=> (filter odd? seq_)
    (1 3 5 7 9)

And also an example why it does not work to go and grab one or another desirable feature from a functional language, they need to work together.

> (Which isn't to say that you can't successfully use some functional techniques in Python. But it's best done in a measured, pragmatic way.)

A great example how it is done right is Python's numpy package. The people who created that knew about functional languages and APL (which fits nicely in since Python's predecessor ABC had some APL smell). The obviously knew what they were doing, and created a highly usable combination of a general data type and powerful operations on it.


I found this very surprising so asked a Python-knowledgeable acquaintance who mentioned that this works as expected

    >>> seq = [x for x in range(1,11)]
    >>> list(filter(lambda x: x % 2 == 0, seq))
    [2, 4, 6, 8, 10]
    >>> list(filter(lambda x: x % 2 == 1, seq))
    [1, 3, 5, 7, 9]


Right. Because it's doing two different things. One is working with lists, the other is working with lazy calculations.

A common functional pattern is to do lazy calculations, so that you don't have to store every result in memory all at once. The subtext I'm getting at is that a language that has footguns that make it dangerous (for reasons of correctness, if not performance) to reuse and compose lazy calculations is a language that is fundamentally ill-suited to actual functional programming.

Which is fine! Python's already a great procedural-OO language. It's arguably the best procedural-OO language. Which is a big part of why it's taken over data science, business intelligence, and operations. Those are, incidentally, the domains where I feel just about the least need to program in a functional paradigm. And, in the domains where I do have a strong preference for FP, Python is already a poor choice for plenty of other reasons. (For example, the global interpreter lock eliminates one of the key practical benefits.) No amount of risky, scarring cosmetic surgery is going to change that.


OK I see, the very short sequence in your example was a stand-in for a potentially infinite one. I got nerd-sniped into understanding what was happening as I found the behavior surprising.


It is very surprising.

And that is sort of another layer to why I think that people trying to turn Python into a functional language should just simmer down. A core principle in functional programming is that you should be able to swap out different implementations of an abstraction without changing the correctness of the program. That's not really something that can be enforced by most functional languages, but it's at least a precedent they set in their standard libraries. But Python's standard library has a different way of doing things, and favors different approaches to abstraction. And those conventions make the Python ecosystem a hostile environment for functional programming.

Hylang is another interesting example. It's a cool language. But watching how it evolved is slightly disheartening. It sought to be a lisp for Python, but the compromises they needed to make to get the language to interact well with Python have hurt a lot of its lisp feel, and make it a kind of peculiar language to try to learn as someone with a lisp background. IIRC, Python's lack of block scoping was an elephant in the room for that project, too.


To be fair, that's a foot gun in haskell as well. Using lists non-linearly like this in haskell gives you the correct results but at a 10x performance tax or worse because it can't optimize into a loop anymore.


At least to me, a footgun goes beyond a mere performance gotcha. There's a whole category difference between, "If you do this, the compiler may not be able to optimize your code as well," and, "If you do this, your code may produce incorrect results."




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

Search: