Fredrik Lundh once suggested the following set of rules for refactoring uses of lambda:
1. Write a lambda function.
2. Write a comment explaining what the heck that lambda does.
3. Study the comment for a while, and think of a name that captures the essence of the comment.
4. Convert the lambda to a def statement, using that name.
5. Remove the comment.There was a limitation in Python where spacing and indentation gets ignored between parentheses, which makes it impossible to pass a multi-line lambda as an argument to a method or function. However, given the new parser, that limitation might be able to be mitigated.
def function1():
print ("hello outer")
def function2():
print ("hello inner")
function2()
function1()
Output: hello outer
hello inner >>> f = lambda x: x * \
... 20
>>>
>>> f
<function <lambda> at 0x7f8b69f2c6a8>
>>> f(2)
40x,y = 1,2
q = list(map(lambda t: (tx := t*x, ty := t*y, tx+ty)[-1], [1, 2, 3]))
Readability counts.
I'm currently about a month into learning a legacy codebase that was written in a functional language. If I could single out one thoroughly egregious practice that has made this code far, far more difficult to read and understand than it should have been, it's multi-line anonymous functions. In general, if a function is doing something complicated enough to need multiple lines of code, it's doing something complicated enough to merit an explicit name. def foo_and_bar(x):
foo(x)
bar(x)
whew! good thing i named thatIME this limitation just leads to throwaway names like
process(x)
go(x)
do_foo(x) # in foo(x)
cb/callback/fn
because a lot of things just don't have sensible names! just like how it'd suck to have to come up with a name for every loop body(although there was some book advocating for "replace every loop body with a named function" so some people enjoy that i guess...)
In this specific case, that single instance of a single pattern is such a throw-away that it doesn't deserve a name, but the pattern itself is easy enough to name. So I'd skip the single-purpose function and create a combinator.
def do_each(*args):
def helper(x):
for fn in args:
fn(x)
return helper
and then, when I need to do both foo and bar, I don't even need a lambda. map(do_each(foo, bar), some_sequence)
That's a fairly specific case, though. Moving back to the general, I would say that a function that does more than one thing, but can't easily be named, is a code smell.Of course, every general rule has its exceptions. But I'm not so keen on the idea of optimizing one's coding style for the exceptional cases. Going back to PEP 20, "Special cases aren't special enough to break the rules."
(I realize mapping a function that returns nothing is terrible, but I'm feeling too lazy to think of a better example.)
[this kind of reminds me of Go's generics mess, where workarounds for lack of generics are "just how you write Go and that's the language's philosophy"... until generics land and suddenly they won't be]
Regardless, I don't think the hypothetical is super useful, because its unstated major premise is, "But what if we, for the sake of argument, ignore all the other good reasons why Python doesn't have them?"
My favorite programming language is functional, and has significant whitespace and multiline anonymous functions. While it is my favorite, I do have to concede that the Python language maintainers' worries about the syntactic implications of multiline lambdas in a whitespace language are accurate.
(I could quote the zen of python some more here, too. Lines 5 and 6.)
maybe it's time to allow optional brackets for marking where a function starts and ends? eg. a { ... } (or anything else -- even {| ... |} will do)
(lambda flask:
(lambda app:
(app,
app.route('/')(
lambda: 'Hello World!'
)
)[0]
)(flask.Flask('__name__')).run()
)(__import__('flask'))
See https://gist.github.com/e000/1023982 for more horrible examplesThe lambda limitations are a product of this plus the particular whitespace rules Python has.
What actually hampers functional expression is semantics and mostly two-fold:
1. functions are terribly slow and always grow stack, so cannot replace iterative constructs.
2. Although python actually comes with a fair amount of functional data structures out of the box (str, bytes, tuple, frozenset, namedtuple, ...) none of them can be "updated" efficiently.
There are some other things (exception as opposed to sum-type based error handling), but fixing the above would be enough to write functional code pretty unhampered, I think.
If they can’t be updated efficiently, they are just immutable than particularly functional.
Python's lambda does pretty much what you would expect from Scheme. It creates a callable the binds arguments to parameters in a lexically scoped namespace and then evaluates the body of the lambda in that namespace. And with an open parenthesis, you can write multiline lambdas and indent it however you want.
All the usual wizardry is possible:
>>> (
lambda n: (lambda fact: fact(n, fact))(
lambda n, inner: 1 if n == 0 else (n * inner(n - 1, inner))
)
)(5)
120
Also, Python has rough equivalents to some special forms in Scheme: (if testexp posexp negexp) ⟶ (posexp if testexp else negexp)
(cond (p1 e1) (p2 e2) (else e3) ⟶ (e1 if p1 else e2 if p2 else e2)
(begin e1 e2 e2) ⟶ [e1, e2, e3][-1]
(and e1 e2 e3) ⟶ (e1 and e2 and e3)
(or e1 e2 e2) ⟶ (e1 or e2 or e3)
Some statements do have a functional form but aren't well known: class keyword ⟶ type(name, bases, namespace)
import keyword ⟶ __import__(name, globals, locals, fromlist)
You have map(), filter(), partial(), and reduce(). The operator module provides function equivalents for most operators. Also, the itertools were directly based on their equivalents in functional languages or array manipulation languages.That said, Python does lack some essential tooling that you would really miss:
- There is no way to create new special forms.
- Some important special forms are missing: let, let*, and letrec
- The language spec precludes tail call optimization.
- Some Python statements lack functional equivalents: try/except, with-statement, and assert-statement (let* ((v1 e1) (v2 e2)) e3) ⟶ ((v1:=e1), (v2:=e2), e3)[-1]
Dr. Racket example: > (let* ((x (+ 3 4)) (y (* x x))) (+ y (- x) 8))
50
Python equivalent: >>> ((x:=3+4), (y:=x**2), y-x+8)[-1]
50- python scoping is not really lexical (and it captures variables, not the values they contain) - python is statement based, so lambdas are less powerful than in scheme.
So not really the same thing.
¹ https://docs.python.org/3/reference/lexical_analysis.html#id...
EDIT: To be clear, I like Raku. But Python has its own distinct aesthetic.
See https://en.wikipedia.org/wiki/Purely_functional_data_structu... and Okasaki’s book Purely Functional Data Structures.
How does this benefit “plumbing”?
My biggest problem with Python is that I either have to write
list(function1(*function2(len(obj.method())))
Or try to avoid all the variables by using classes... which is fine... if it wasn’t for self taking up roughly a third of the word count.
There’s no good way to add support for full anonymous functions to Python’s grammar. One of the rules that makes significant-whitespace work elegantly is that statements can contain expressions, but never vice-versa.
x => x * 2
instead of lambda x : x * 2Perhaps with the move to a PEG parser, this syntax could now be supported?
λx: x*2- Can only have one line
- Can only use expressions, not statements. E.g. `print`s, loops, conditionals are out.
- Overall just kinda clunky
Here's an SO post about lambdas where the answer is "Use def instead." https://stackoverflow.com/questions/14843777/how-to-write-py...
println = lambda s: sys.stdout.write(s + '\n')
Not that it really makes things much better, but, at least it shows you can do it. In [4]: f = lambda x: "Yes" if x else "No"
In [5]: f(True)
Out[5]: 'Yes'
In [6]: f(False)
Out[6]: 'No'
It's Python's version of ternary operators, so not sure if that counts as a "true" conditional; but it is one.Loops don't work, but list comprehensions do, and they are definitely the way to go here. Multi-line loops deserve a `def`.
print is a function (and thus can be used in expressions)
python has conditional expressions (<true-val> if <cond> else <false-val>)
loops are a limitation, though comprehensions, map(), functools.reduce(), and the itertools module can allow lots of looping functionality in an expression.