Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 246
stefan Avatar asked Apr 29 '18 20:04

stefan


3 Answers

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.

like image 126
abarnert Avatar answered Nov 14 '22 01:11

abarnert


There is a syntax problem: an assignment is a statement, and the body of a lambda can only have expressions. Python's syntax is designed this way1. Check it out at https://docs.python.org/3/reference/grammar.html.

There is also a semantics problem: what does each statement return?

I don't think there is interest in changing this, as lambdas are meant for very simple and short code. Moreover, a statement would allow sequences of statements as well, and that's not desirable for lambdas.

It could be also fixed by selectively allowing certain statements in the lambda body, and specifying the semantics (e.g. an assignment returns None, or returns the assigned value; the latter makes more sense to me). But what's the benefit?

Lambdas and functions are interchangeable. If you really have a use-case for a particular statement in the body of a lambda, you can define a function that executes it, and your specific problem is solved.


Perhaps you can create a syntactic macro to allow that with MacroPy3 (I'm just guessing, as I'm a fan of the project, but still I haven't had the time to dive in it).

For example MacroPy would allow you to define a macro that transforms f[_ * _] into lambda a, b: a * b, so it should not be impossible to define the syntax for a lambda that calls a function you defined.


1 A good reason to not change it is that it would cripple the syntax, because a lambda can be in places where expressions can be. And statements should not. But that's a very subjective remark of my own.

like image 8
fferri Avatar answered Nov 14 '22 02:11

fferri


My answer is based on chepner's comment above and doesn't draw from any other credible or official source, however I think that it will be useful.

If assignment was allowed in lambda expressions, then the error of confusing == (equality test) with = (assignment) would have more chances of escaping into the wild.

Example:

>>> # Correct use of equality test
... list(filter(lambda x: x==1, [0, 1, 0.0, 1.0, 0+0j, 1+0j]))
[1, 1.0, (1+0j)]

>>> # Suppose that assignment is used by mistake instead of equality testing
... # and the return value of an assignment expression is always None
... list(filter(lambda x: None, [0, 1, 0.0, 1.0, 0+0j, 1+0j]))
[]

>>> # Suppose that assignment is used by mistake instead of equality testing
... # and the return value of an assignment expression is the assigned value
... list(filter(lambda x: 1, [0, 1, 0.0, 1.0, 0+0j, 1+0j]))
[0, 1, 0.0, 1.0, 0j, (1+0j)]
like image 3
Leon Avatar answered Nov 14 '22 01:11

Leon