• Martin Thoma
  • Home
  • Categories
  • Tags
  • Archives
  • Support me

Closures

Contents

  • Closures
    • Pairs
    • partial

A closure is a technique for implementing lexically scoped name binding in a language with first-class functions.

Pairs

This post is triggered by this question and the nice answer by Martijn Pieters.

Here is a super short example:

def cons(a, b):
    def pair(f):
        return f(a, b)

    return pair


def car(pair):
    def return_first(x, y):
        return x

    return pair(return_first)


def cdr(pair):
    def return_last(a, b):
        return b

    return pair(return_last)


cons_return = cons(42, 1337)
print(cons_return)
print(car(cons_return))
print(cdr(cons_return))

What is the output of the 3 print statements? Why?

As there are not too many possible answers, you can easily guess it. But can you explain it?

Explanation:

  1. pair is a function, hence cons returns a function. Note that it returns the function, not the return value of the function. So the first output is something like <function cons.<locals>.pair at 0x7f63aa5fa840>.
  2. Once pair is called, a and b are set in this name space. Hence cons can return different functions!
  3. car and cdr are basically the same, so let's just focus on car.
  4. car has a function as its parameter. It calls this function and gives it a function that wants two parameters. This parameter function just returns its first argument.
  5. Once car is called, the new function pair is called with the local context of cons. Then return_first is called within that context.

Once you print some intermediate results you get a better understanding:

def cons(a, b):
    print("cons called with a={}, b={}".format(a, b))

    def pair(f):
        print("pair called with a={}, b={}, f={}".format(a, b, f))
        return f(a, b)

    return pair


def car(pair):
    print("car called with pair={} (a, b are undefined)".format(pair))

    def return_first(x, y):
        print("return_first called with x={}, y={}".format(x, y))
        return x

    return pair(return_first)


def cdr(pair):
    def return_last(a, b):
        return b

    return pair(return_last)


cons_return = cons(42, 1337)
print(cons_return)
print()
print(car(cons_return))

gives

cons called with a=42, b=1337
<function cons.<locals>.pair at 0x7fbb5fe7c840>

car called with pair=<function cons.<locals>.pair at 0x7fbb5fe7c840> (a, b are undefined)
pair called with a=42, b=1337, f=<function car.<locals>.return_first at 0x7fbb5fe7c8c8>
return_first called with x=42, y=1337
42

I don't know about you, but this really gives me headaches. I'm don't know why, but closures are super popular in JavaScript.

Let's see the one example I know they are super useful for.

partial

Pythons functools.partial can be used to create simpler functions where some of the arguments are already filled.

It's implementation is roughly:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*args, *fargs, **newkeywords)

    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

And you can use it like this:

from functools import partial


def power(base, exponent):
    return base ** exponent


basetwo = partial(power, base=2)
print(basetwo(exponent=10))  # prints 1024

Please note that the call basetwo(10) gives the error

Traceback (most recent call last):
  File "/home/moose/foobar.py", line 8, in <module>
    print(basetwo(10))  # prints 1024
TypeError: power() got multiple values for argument 'base'

as you supply power both as a keyword-argument and as a position-based argument.


Published

Okt 19, 2018
by Martin Thoma

Category

Code

Tags

  • Python 141
  • Software Engineering 19

Contact

  • Martin Thoma - A blog about Code, the Web and Cyberculture
  • E-mail subscription
  • RSS-Feed
  • Privacy/Datenschutzerklärung
  • Impressum
  • Powered by Pelican. Theme: Elegant by Talha Mansoor