Hacker News new | ask | show | jobs
by substack 5651 days ago
Frankly I've found python to be quite antagonistic to functional programming with very weak lambdas and poor building blocks for building interfaces in monadic pipeline style. I guess coming from java it would seem refreshing but coming back from haskell or even just javascript it feels like a straitjacket.
2 comments

I hear this argument a lot, but I don't know these concepts well enough from haskell et al to fully understand it. Could you be more specific about this? Perhaps, provide a direct example?
I think one key thing is that lambdas are second-class citizens in Python in terms of the kinds of code you can express. Python lambdas can only contain one single expression and nothing else. For more complicated functions, in particular any function that requires statements, you are always required to use nested named functions, e.g.

    myList = [[1, 2], [3, 4], [5, 6]]
    for elem in myList:
        del elem[0]
cannot be done using a lambda, as

    map(lambda elem: del elem[0], myList)
results in an error. Until Python 3, that also meant that

    map(lambda elem: print elem, myList)
was also a syntax error because of the print statement. For a more elaborate example, consider the following JavaScript function which returns a function that increments a counter every time its called and returns the value before it was incremented:

    function makeCounter() {
        var value = 0;
        return function() {
            return value ++;
        }
    }
You would use this as follows:

    > c = makeCounter();
    > c();
    0
    > c();
    1
    ... and so forth
There is no way to implement this in Python using lambda; the closest equivalent (assuming Python 3 for the nonlocal keyword) is

    def makeCounter():
        value = 0
        def count():
            nonlocal value
            tmp, value = value, value + 1
            return tmp
        return count
because there's no way to have that assignment take place in a lambda.

Of course, Haskell has neither state nor statements, so the above examples don't literally show why Haskell's lambdas are necessarily superior, but the point isn't that Python disallows assignment in lambdas; it's that Python's lambdas can only express a subset of possible functions, while JavaScript and Haskell (and Scheme &al.) don't have that limitation.

Why not use a list comprehension for that? Like:

  my_list = [[1, 2], [3, 4], [5, 6]]
  my_other_list = [ elem[1:] for elem in my_list ]
You could also reassign the my_list name to your processed list, but changing the state of things (you are not really changing anything here, just calling something different by an already known name) is very un-functional.

As for your second example, I would consider using "yield".

I agree wholeheartedly with all of that; in general, actually, I think lambdas in Python should be used sparingly, if at all, and I think Python makes up for any limitations with its other features (e.g. generators, function decorators, list comprehensions, &c.)

The issues with Python's lambdas really come up when you start trying to do things the Haskell-esque way—which is to say, the super-functional category-theory-on-the-brain way—which can be the right solution to certain problems (e.g. monadic parsers) but tends to be difficult to express in a way that's pleasant, non-hacky, and Pythonic. But I am of the personal opinion that Python's design choices make sense and I don't want to rail against it for not being Haskell or Ruby; I just wanted to show how anonymous functions in particular differ from language to language.

Excellent explanation. I think most people's issue with python's lambda is that it isn't what they expect. There are generally some gotchas and the syntax diverges from that of a regular function.

I side step the lack of closures by using named locals with the keyword arguments when defining inner functions. However, for numeric types, this doesn't really work:

    def makeCounter():
        value = 0
        def count(value=value):
            tmp, value = value, value + 1
            return tmp
        return count

    inc = makeCounter()
    print inc()
    print inc()
The above prints '0\n0', but if we use an object that is used "by reference" in a such a function, one can get this behavior:

    def set_adder():
        s = set()
        def add(item, s=s):
            s.add(item)
            return s
        return add

    s = set_adder()
    print s(1)
    print s(2)

    t = set_adder()
    print t(3)
    print t(4)
This will print:

    $ python mk.py  
    set([1])  
    set([1, 2])
    set([3])
    set([3, 4])
Python actually does have proper lexical scope and real closures (as of version 2.2, I think), so you can do

    def set_adder():
        s = set()
        def add(item):
            s.add(item)
            return s
        return add
and it'll work the same way. The problem really comes in the fact that before Python 3, all assignment took place in the local scope, so anything involving assignment (such as the counter example) doesn't work. You can sidestep it in Python 2.6 using the same kind of trick:

    def make_counter():
        value = [0]
        def count():
            temp, = value
            value[0] += 1
            return temp
        return count
which is sort of hacky but works. (Other languages sidestep this by having some other way of specifying declaration-versus-assignment, such as JavaScript's var keyword for variable declaration.)
Your comment provoked me to try Haskell and see what the fuss is all about. I came across this fun little Try Haskell thing (yeah, copycat of TryRuby) http://tryhaskell.org/ It's very interactive and fun.

I think basically what the parent comment is saying is that it's much easier to pass functions as arguments to other functions in Haskell than it is in python. They call them high order functions.

I quite agree with you (especially after writing a lot of javascript) - it's funny you mentioned javascript as I said the same thing in a child comment I made below :-)