Why are assignments not allowed in Python's `lambda` expressions?

stefan picture stefan · Apr 29, 2018 · Viewed 12.6k times · Source

This is not a duplicate of Assignment inside lambda expression in Python, i.e., I'm not asking how to trick Python into assigning in a lambda expression.

I have some λ-calculus background. Considering the following code, it looks like Python is quite willing to perform side-effects in lambda expressions:

#!/usr/bin/python

def applyTo42(f):
    return f(42)

def double(x):
    return x * 2

class ContainsVal:
    def __init__(self, v):
        self.v = v

    def store(self, v):
        self.v = v

def main():

    print('== functional, no side effects')

    print('-- print the double of 42')
    print(applyTo42(double))

    print('-- print 1000 more than 42')
    print(applyTo42(lambda x: x + 1000))

    print('-- print c\'s value instead of 42')
    c = ContainsVal(23)
    print(applyTo42(lambda x: c.v))


    print('== not functional, side effects')

    print('-- perform IO on 42')
    applyTo42(lambda x: print(x))

    print('-- set c\'s value to 42')
    print(c.v)
    applyTo42(lambda x: c.store(x))
    print(c.v)

    #print('== illegal, but why?')
    #print(applyTo42(lambda x: c.v = 99))

if __name__ == '__main__':
    main()

But if I uncomment the lines

    print('== illegal, but why?')
    print(applyTo42(lambda x: c.v = 99))

I'll get

SyntaxError: lambda cannot contain assignment

Why not? What is the deeper reason behind this?

  • As the code demonstrates, it cannot be about “purity” in a functional sense.

  • The only explanation I can imagine is that assignemts do not return anything, not even None. But that sounds lame and would be easy to fix (one way: make lambda expressions return None if body is a statement).

Not an answer:

  • Because it's defined that way (I want to know why it's defined that way).

  • Because it's in the grammar (see above).

  • Use def if you need statements (I did not ask for how to get statements into a function).

“This would change syntax / the language / semantics” would be ok as an answer if you can come up with an example of such a change, and why it would be bad.

Answer

abarnert picture abarnert · Apr 29, 2018

The entire reason lambda exists is that it's an expression.1 If you want something that's like lambda but is a statement, that's just def.

Python expressions cannot contain statements. This is, in fact, fundamental to the language, and Python gets a lot of mileage out of that decision. It's the reason indentation for flow control works instead of being clunky as in many other attempts (like CoffeeScript). It's the reason you can read off the state changes by skimming the first object in each line. It's even part of the reason the language is easy to parse, both for the compiler and for human readers.2

Changing Python to have some way to "escape" the statement-expression divide, except maybe in a very careful and limited way, would turn it into a completely different language, and one that no longer had many of the benefits that cause people to choose Python in the first place.

Changing Python to make most statements expressions (like, say, Ruby) would again turn it into a completely different language without Python's current benefits.

And if Python did make either of those changes, then there'd no longer be a reason for lambda in the first place;2,3 you could just use def statements inside an expression.


What about changing Python to instead make assignments expressions? Well, it should be obvious that would break "you can read off the state changes by skimming the first object in each line". Although Guido usually focuses on the fact that if spam=eggs is an error more often than a useful thing.

The fact that Python does give you ways to get around that when needed, like setattr or even explicitly calling __setitem__ on globals(), doesn't mean it's something that should have direct syntactic support. Something that's very rarely needed doesn't deserve syntactic sugar—and even more so for something that's unusual enough that it should raise eyebrows and/or red flags when it actually is done.


1. I have no idea whether that was Guido's understanding when he originally added lambda back in Python 1.0. But it's definitely the reason lambda wasn't removed in Python 3.0.

2. In fact, Guido has, multiple times, suggested that allowing an LL(1) parser that humans can run in their heads is sufficient reason for the language being statement-based, to the point that other benefits don't even need to be discussed. I wrote about this a few years ago if anyone's interested.

3. If you're wondering why so many languages do have a lambda expression despite already having def: In many languages, ranging from C++ to Ruby, function aren't first-class objects that can be passed around, so they had to invent a second thing that is first-class but works like a function. In others, from Smalltalk to Java, functions don't even exist, only methods, so again, they had to invent a second thing that's not a method but works like one. Python has neither of those problems.

4. A few languages, like C# and JavaScript, actually had perfectly working inline function definitions, but added some kind of lambda syntax as pure syntactic sugar, to make it more concise and less boilerplatey. That might actually be worth doing in Python (although every attempt at a good syntax so far has fallen flat), but it wouldn't be the current lambda syntax, which is nearly as verbose as def.