Functional Programming 3

Introduction

In this third lesson on functional programming follow a string of related techniques. We first look at closures and compare them to classes and objects and thereby bridge the gap between functional programming and object oriented programming. We then see how closures form the basis of function currying, and in turn how function currying facilitates functional composition.

We wrap up our series of functional programming lessons with an overview of the Itertools and Functools libraries which facilitate the techniques covered across all three lessons.

Learning Objectives

Upon successful completion of this lesson, you will be able to:

  • define a closure as a factory for creating stand-alone methods.
  • describe the arity of a function.
  • curry a function to reduce its arity, its number of arguments in particular, and thereby make it suitable for functional composition.
  • use the Itertools library.
  • use the Functools library.

New Words or Concepts

  • Closure
  • Scope
  • Curry
  • Arity
  • Functional Composition
  • Itertools
  • Functools

Optional Reading

Content

Closures

The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said “Master, I have heard that objects (and classes) are a very good thing - is this true?” Qc Na looked pityingly at his student and replied, “Foolish pupil - objects are merely a poor man’s closures.”

Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire “Lambda: The Ultimate…” series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.

On his next walk with Qc Na, Anton attempted to impress his master by saying “Master, I have diligently studied the matter, and now understand that objects are truly a poor man’s closures.” Qc Na responded by hitting Anton with his stick, saying “When will you learn? Closures are a poor man’s object.” At that moment, Anton became enlightened.

http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent

What exactly are Closures, these mysterious things that offer programming enlightenment? Before we consider a formal definition, let’s continue to compare and contrast closures with objects.

  • Objects have methods.
  • Closures are methods — they are defined and behave like functions, but like object methods they carry internal state and take it into account when returning results.
  • Objects can, and generally do, carry mutable state.
  • Closures can, and often do, carry mutable state.
  • Objects control access to their attributes — their internal state — through Properties and Python’s lexical scoping rules. By default however object attributes are externally accessible.
  • Closures by nature tend to close around their internal state and thereby prevent external access, thus in terms of access to internal state, internal attributes, this is the opposite of the default behavior of an object. In accordance with Python’s Consenting Adults policy a closure’s internal state is still accessible via its __closure__ dunder, but this violates the spirit of a closure — so do so at your own risk.

Thus, objects (or classes) and closures are similar, but different.

This is the general form of a closure:

def closure(internal_state):  # line 1
    def return_function(arguments):  # line 2
        return internal_state combined with arguments  # line 3
    return return_function  # line 4

Let’s unpack that line by line.

  1. The closure is defined like any other function with a name and arguments. In this case the name of the function is closure and its arguments are internal_state.
  2. Inside the closure another function is defined. It too takes arguments. In this case its name is return_function, because this internally defined function itself will be returned by the closure.
  3. When calculating a return value the internal function, return_function, uses both the internal state passed into the closure on line 1 when the closure was first defined, and also the arguments that will be passed into it later when it is used as a stand-alone function.
  4. The closure uses the internally defined function, return_function for its return value. Thus, just as a class is a template or factory for creating stateful objects, a closure is a template or factory for creating stateful functions, that is, stand-alone methods.

Functions Within Functions

We’ve been defining functions within functions to explore namespace scope. But functions are “first class objects” in python, so we can not only define them and call them, but we can assign names to them and pass them around like any other object.

So after we define a function within a function, we can actually return that function as an object:

def counter(start_at=0):
    count = start_at
    def incr():
        nonlocal count
        count += 1
        return count
    return incr

So this looks a lot like the previous examples, but we are returning the function that was defined inside the function.

What’s going on here?

We have passed the start_at value into the counter function.

We have stored it in counter’s scope as a local variable: count

Then we defined a function, incr that adds one to the value of count, and returns that value.

Note that we declared count to be nonlocal in incr’s scope, so that it would be the same count that’s in counter’s scope.

What type of object do you get when you call counter()?

In [37]: c = counter(start_at=5)

In [38]: type(c)
Out[38]: function

So we get a function back – makes sense. The def defines a function, and that function is what’s getting returned.

Being a function, we can, of course, call it:

In [39]: c()
Out[39]: 6

In [40]: c()
Out[40]: 7

Each time is it called, it increments the value by one – as you’d expect.

But what happens if we call counter() multiple times?

In [41]: c1 = counter(5)

In [42]: c2 = counter(10)

In [43]: c1()
Out[43]: 6

In [44]: c2()
Out[44]: 11

So each time counter() is called, a new incr function is created. But also, a new namespace is created, that holds the count name. So the new incr function is holding a reference to that new count name.

This is what makes in a “closure” – it carries with it the scope in which is was created.

The returned incr function is a “curried” function – a function with some parameters pre-specified.

Let’s experiment a bit more with these ideas:

play_with_scope.py

Currying

“Currying” is a special case of closures:

The idea behind currying is that you may have a function with a number of parameters, and you want to make a specialized version that function with a couple parameters pre-set.

Real world Example

I was writing some code to compute the concentration of a contaminant in a river, as it was reduced by exponential decay, defined by a half-life:

https://en.wikipedia.org/wiki/Half-life

So I wanted a function that would compute how much the concentration would reduce as a function of time – that is:

def scale(time):
    return scale_factor

The trick is, how much the concentration would be reduced depends on both time and the half life. And for a given material, and given flow conditions in the river, that half life is pre-determined. Once you know the half-life, the scale is given by:

scale = 0.5 ** (time / (half_life))

So to compute the scale, I could pass that half-life in each time I called the function:

def scale(time, half_life):
    return 0.5 ** (time / (half_life))

But this is a bit klunky – I need to keep passing that half_life around, even though it isn’t changing. And there are places, like map that require a function that takes only one argument!

What if I could create a function, on the fly, that had a particular half-life “baked in”?

Enter Currying – Currying is a technique where you reduce the number of parameters that function takes, creating a specialized function with one or more of the original parameters set to a particular value. Here is that technique, applied to the half-life decay problem:

def get_scale_fun(half_life):
    def half_life(time)
        return 0.5 ** (time / half_life)
    return half_life

NOTE: This is simple enough to use a lambda for a bit more compact code:

def get_scale_fun(half_life):
    return lambda time: 0.5 ** (time / half_life)
Using the Curried Function

Create a scale function with a half-life of one hour:

In [8]: scale = get_scale_fun(1)

In [9]: [scale(t) for t in range(7)]
Out[9]: [1.0, 0.5, 0.25, 0.125, 0.0625, 0.03125, 0.015625]

The value is reduced by half every hour.

Now create one with a half life of 2 hours:

In [10]: scale = get_scale_fun(2)

In [11]: [scale(t) for t in range(7)]
Out[11]:
[1.0,
 0.7071067811865476,
 0.5,
 0.3535533905932738,
 0.25,
 0.1767766952966369,
 0.125]

And the value is reduced by half every two hours…

And it can be used with map, too:

In [13]: list(map(scale, range(7)))
Out[13]:
[1.0,
 0.7071067811865476,
 0.5,
 0.3535533905932738,
 0.25,
 0.1767766952966369,
 0.125]

functools.partial

The functools module in the standard library provides utilities for working with functions:

https://docs.python.org/3.5/library/functools.html

Creating a curried function turns out to be common enough that the functools.partial function provides an optimized way to do it:

What functools.partial does is:

  • Makes a new version of a function with one or more arguments already filled in.
  • The new version of a function documents itself.

Example:

def power(base, exponent):
    """returns based raised to the give exponent"""
    return base ** exponent

Simple enough. but what if we wanted a specialized square and cube function?

We can use functools.partial to partially evaluate the function, giving us a specialized version:

square = partial(power, exponent=2)
cube = partial(power, exponent=3)

Quiz

  1. Closures can carry mutable state.

    True
    False
  2. Closures encourage direct, external access of their internal attributes.

    True
    False
  3. What is arity?

    Arity is a condition that describes the over-inflation of a call to functools.partial.
    Arity is when too few closures have been defined within a system.
    Arity relates to the number and types of arguments expected by a function.
  4. What is the purpose of currying?

    To hide values inside objects.
    To add spice to your code.
    To reduce the number of arguments to a function and thereby make it more composable.
  5. Python offers a library function to facilitate currying. What is it called?

    __init__()
    __closure__()
    functools.partial()

Activity & Assignment

Generators

Last week we looked at Spotify’s top tracks from 2017. We used comprehensions and perhaps a lambda to find tracks we might like. Having recovered from last week’s adventure in pop music we’re ready to venture back.

Write a generator to find and print all of your favorite artist’s tracks from the data set. Your favorite artist isn’t represented in that set? In that case, find Ed Sheeran’s tracks.

Load the data set following the instructions from last week. Submit your generator expression and the titles of Ed’s tracks.

Closures

Using the same data set, write a closure to capture high energy tracks. We will define high energy tracks as anything over 8.0. Submit your code and the tracks it finds, artist name, track name and energy value.

Indices and tables