Also, Python is a wonderful functional language when used functionally.
Also, Python is a wonderful functional language when used functionally.
Python having a statement heavy syntax and making complex expressions (while possible) awkward is the problem with its anonymous functions, not the fact that its anonymous functions are limited to a single expression.
f = lambda x: [
x + y
for y in range(x)
if y % 2 == 0
]
>>> f(5)
[5, 7, 9]
Lambdas which perform multiple sequential steps are fine, since we can use tuples to evaluate expressions in order; e.g. from sys import stdout
g = lambda x: (
stdout.write("Given {0}\n".format(repr(x))),
x.append(42),
stdout.write("Mutated to {0}\n".format(repr(x))),
len(x)
)[-1]
>>> my_list = [1, 2, 3]
>>> new_len = g(my_list)
Given [1, 2, 3]
Mutated to [1, 2, 3, 42]
>>> new_len
4
>>> my_list
[1, 2, 3, 42]
The problem is that many things in Python require statements, and lambdas cannot contain any; not even one. For example, all of the following are single lines: >>> throw = lambda e: raise e
File "<stdin>", line 1
throw = lambda e: raise e
^^^^^
SyntaxError: invalid syntax
>>> identity = lambda x: return x
File "<stdin>", line 1
identity = lambda x: return x
^^^^^^
SyntaxError: invalid syntax
>>> abs = lambda n: -1 * (n if n < 0 else return n)
File "<stdin>", line 1
abs = lambda n: -1 * (n if n < 0 else return n)
^^^^^^
SyntaxError: invalid syntax
>>> repeat = lambda f, n: for _ in range(n): f()
File "<stdin>", line 1
repeat = lambda f, n: for _ in range(n): f()
^^^
SyntaxError: invalid syntax
>>> set_key = lambda d, k, v: d[k] = v
File "<stdin>", line 1
set_key = lambda d, k, v: d[k] = v
^^^^^^^^^^^^^^^^^^^^
SyntaxError: cannot assign to lambda
>>> set_key = lambda d, k, v: (d[k] = v)
File "<stdin>", line 1
set_key = lambda d, k, v: (d[k] = v)
^^^^
SyntaxError: cannot assign to subscript here. Maybe you meant '==' instead of '='?Don't need return in a lambda. Assignment now has the walrus. Leaves raise and few other odds and ends.
Do believe you've cracked it! Don't think I'll use it much, but you never know... in a pinch.
You're right that the walrus makes assignment more usable; we can also call methods like .__setitem__ to get similar effects. Unfortunately the walrus seems to suffer the same broken/ambiguous scoping as assignment statements, e.g.
>>> a = 1
>>> b = lambda: (print(a), a := 2)
>>> b()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <lambda>
UnboundLocalError: cannot access local variable 'a' where it is not associated with a value
> Don't need return in a lambda`return` is needed for early returns. Notice that my `abs` example is trying to do that with `return n` (to skip the `-1 *` operation, since `n` is non-negative in that case).
> Leaves raise and a few other odds and ends
Off the top of my head: yield, match, def, import, class, with, for, while, assert, try, except, async, await.
b = lambda: (a := 2, print(a))
Hmm, need to put the assignment before. Can't access nonlocals unless you only read the the var and don't write to it. That's the way functions in python work as well.No, that would not have the intended behaviour: your `print(a)` is reading a fresh local variable, defined by the `:=` expression (which shadows that from the outer scope), so it will output '2'. The intended behaviour is to print '1' and reassign the outer name; but we can't do that (that's why I chose it as an example!)
> Can't access nonlocals unless you only read the the var and don't write to it. That's the way functions in python work as well.
Yes, that is precisely what I meant when I said it "suffers the same broken/ambiguous scoping as assignment statements".
Python took a haphazard, WorseIsBetter approach to assignment/declaration/scoping. The resulting behaviour is complex, confusing and involves spooky action-at-a-distance (e.g. the meaning of my `print(a)` expression was altered by the presence of an ':=' expression which hadn't been reached yet!). Since this doesn't really affect simple "top to bottom" scripts, it only became an issue once large applications and frameworks started emerging; by that time it was too late to fix, since there was too much Python code in the wild to justify such a large breaking change :(
However, I don't think complex functionality belongs in them either, so not a big loss. See the Beyonce rule mentioned elsewhere in this thread.
Crippled lambdas, no currying, "match" is a clumsy statement, weird name spaces and a rigid whitespace syntax. No real immutability.
"As such, curry is more suitably defined as an operation which, in many theoretical cases, is often applied recursively, but which is theoretically indistinguishable (when considered as an operation) from a partial application."
(while I see partial as having value, I'm struggling to see if currying would really be a useful addition to a scripting language)
That plus named tuples and a little discipline gets me 80+% of the benefits of pure functional.
The biggest thing to me wasn't in your list: lack of tail call optimization means you have to be a bit careful about where you use recursion.
I personally think long anonymous functions are an anti-pattern, naming your functionality is great for readability. I learned this from Wolfram language which makes heavy use of anonymous functions, I would often find myself returning from lunch to find my code which was perfectly clear that morning had become unintelligible. Today I try to limit anonymous functions to re-ordering arguments or "picking" functions that pull simple values out of more complicated data structures.
[0]:https://en.wikipedia.org/wiki/Miranda_(programming_language)...