LAMBDA IN Python has been derived from functional programming, however
there is nothing that lambda can do that can’t be done with the usual
def foo(*args, **kwargs): ... syntax. At the advent of Python3 Guido proposed
to remove lambda from Python,
but relented and allowed it to stay.
Haskell is a canonical functional programming language, it relies heavily on
lambda calculus, although it does have a lambda syntax :
(\x -> x + 1), this is equivalent to
(+1). How can this be? Every function is a lambda function.
We’ll take a quick look at lambda calculus using Python. Then I’ll claim we
don’t use lambda calculus in Python, despite
all the great advantages it gives Haskell programmers. Finally we’ll look at how we
lambda in Python and discuss why it should have been changed going into Python3.
I know this will be an aggravating read for many pythonistas, but please try to remain calm and keep an open mind. Don’t comment without reading first!
$$ (\lambda x. \lambda y. x + y)(2)(3) = (\lambda y. 2 + y)(3) = 2 + 3 $$
LAMBDA CALCULUS (aka. $\lambda$-calculus)
is Turing Complete, meaning we can make programs from just lambda. In lambda calculus, the
lambda x: x would be written as $\lambda x.x$.
In Python we have some syntactic sugar,
lambda x, y: x + y is actually accepting a tuple
of arguments. In the examples I’m being strict and passing arguments individually as per the
maths syntax $\lambda x. \lambda y. x + y$.
To resolve a lambda expression, we substitute in arguments. I’ve shown it in Python syntax so you can run the functions.
(lambda x: (lambda y: x + y))(2)(3) (lambda y: 2 + y)(3) 2 + 3
You can use this syntax to create all manner of programs because you can substitute functions as well as values.
Here we have a function that accepts another function and a single argument. It is given a function that adds 1 to a single argument, and then it is given 4 as an argument.
(lambda f: (lambda a: f(a)))(lambda x: x + 1)(4) (lambda a: (lambda x: x + 1)(a))(4) (lambda a: a + 1)(4) 4 + 1
As a glimpse into what can be done with lambda calculus, here’s an ‘if-then-else’ branching function where we’ve broken the rules of lambda calculus by naming the functions to aid readability.
For more about lambda calculus in Python syntax, I’d recommend a talk from EuroPython 2017 by Anjana Vakil, called “Mary had a little lambda”.
TRUE = lambda x: lambda y: x FALSE = lambda x: lambda y: y IFELSE = lambda p: lambda a: lambda b: p(a)(b) print(IFELSE(TRUE)(1)(2)) # Output: 1 print(IFELSE(FALSE)(1)(2)) # Output: 2
Lambda Calculus: +
LAMBDA, WHEN used in a programming language to define functions gives you currying and thus partial application built in. If you’ve read Python Partial: Code Your Intention, you’ll know I’m a big fan of this.
Lambda calculus can provide features we find in Python, which we know and love, such as first-class functions and higher-order functions, because we can pass functions around.
It can also be used for lazy evaluation: the substitutions can be undertaken without executing the functions. This also allows a compiler, such as the Haskell compiler, to reduce the computation required when composing functions together.
No Lambda in Python
I LOVE the benefits of lambda calculus, I love Python. The two do not mix. In Python our functions are not curried or evaluated using lambda calculus, we know this because functions need all their arguments when called. Consider the difference between these two addition functions.
l_add needs the arguments passed one at a time. We never see this syntax with multiple parenthesis
in Python. It works and it has many benefits built in, but it would be a paradigm
shift to expect pythonistas to start writing their programs this way, it’s just
def add(a, b): return a + b l_add = lambda a: lambda b : a + b add(1, 2) l_add(1)(2)
Compare this to Haskell, which is built for using lambda calculus. Haskell’s syntax is less verbose, easier to write, there’s no annoying parenthesis and the benefits of currying are built in.
-- declaring the function l_add a b = a + b -- calling the function l_add 1 2 -- partial application incr = l_add 1 -- calling the partial function incr 2
But That’s Not How We Use Lambda In Python
I KNOW! In Python we call our functions with all their arguments at the same time, even
lambda x, y: x + y is expecting both arguments together. Typical use cases in Python are for very short inline functions, usually when being passed to a higher-order function.
During the debate about removing lambda from Python3,
Kay Schluehr proposed an inline syntax,
it should be possible to make use of the use of
inline to create anonymous
functions that fulfil our requirement, encode our intention, and stay pythonic.
lambda is a short,
inline, anonymous function. But, an anonymous function is not a calculus.
# Typical use cases map(lambda x: x + 1, range(5)) max(my_tuples, key=lambda t: t) # More pythonic? Closer to the user intent? map(inline _(x): x + 1, range(5)) max(my_tuples, key=inline _(t): t)
PYTHON IS capable
of lambda calculus, but it is not considered pythonic to use it. If a
datetime were to use lambda calculus there would be outcry from the community,
no-one would want to call functions with a set of parenthesis for each argument.
But the normal way to call a function could be made syntactic sugar for
the lambda calculus way. Just like in Haskell, Python would not need
lambda syntax. This imaginary case reveals how the
lambda syntax is not being
used appropriately. If
def foo(x): ... were this syntactic sugar, how would we declare our inline anonymous functions?
# Normal python call: datetime.date(2018, 3, 4) # Lambda calculus call: datetime.date(2018)(3)(4)
If you like that syntactic sugar, check out toolz curry. Wouldn’t it be nice if we didn’t have to use the decorator?
IF EVERY function were a lambda function with some syntactic sugar, we’d still
need a way to declare inline anonymous functions. This brings us back to needing
inline _(): .... This also brings back my argument for all
the functional programming techniques I have covered in this blog so far: Code
the authors intention.
In Python, we do not use
lambda with the intention of using lambda calculus. Our
syntax needs to encode our intention in the places we have used it, and where we will use it.
These use cases are inline, anonymous functions.