Guido was right, there should be no lambda in Python.

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 use 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 Calculus

$$ (\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 function 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 not Pythonic.

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 _ with inline to create anonymous functions that fulfil our requirement, encode our intention, and stay pythonic.

In Python 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[1])

# More pythonic? Closer to the user intent?
map(inline _(x): x + 1, range(5))
max(my_tuples, key=inline _(t): t[1])

Add Sugar

PYTHON IS capable of lambda calculus, but it is not considered pythonic to use it. If a library like 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 a 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?

Conclusion

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 something like 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.