Comp 112

Lecture 13

Function Literals and Higher-Order Functions


Literal Expressions

In Python, we can write expressions, called literals, for:

Lambda Expressions

We can also write literal expressions for functions:

This is the function that doubles its argument (here called x)

x2*xx ⟼ 2 * x

It is equivalent to:

The terminology “lambda” is historical: it comes from the original model of universal computation, called “λ-calculus”, which was invented in the 1930s, before computers even existed.

The general form of a lambda expression is:

Function Composition

One of the most fundamental operations on functions is function composition: using the output from one function as the input for another.

We can write the function composition operation itself as a higher-order function:

Letting us write:

Now we can compose functions without using def or lambda.

The Identity Function

This function is pretty boring, but very useful for building higher-order functions.

Compound Compositions

Using induction, identity and compose we can define more complex patterns of function composition:

(n,f)lambdax:f(f((f(n1)times(fntimes(x)))))(n , f) \quad ⟼ \quad \mathrm{lambda} ~ x ~ : ~ \overbrace{\underbrace{f (f (⋯ (f } _ {(n-1) ~ \mathrm{times}}(f} ^ {n ~ \mathrm{times}} (x)))⋯))

Partial Application of Functions

We can turn a function that expects two arguments into a function that expects the first argument and returns a function expecting the next one:

Instead of doing this by hand, we can write a higher-order function to do this for any two-argument function. This is called function currying:

Aside: using induction, we can curry functions with any number of arguments (although this is a bit tricky).

Curried Higher-Order Functions

We can curry higher-order functions like map and filter:

This makes them even more useful because then we can partially apply them to easily form new functions:

With higher-order functions we can write useful bits of programs with no loops and no recursion.

The ability to manipulate functions just like any other kind of data greatly expands our power of expression.

Example: Rat Diets

Recall the homework problem of finding the average weight of rats on a given diet:

data = \
[   #    name      ,    diet        , weight
    ('Whiskers'    , 'rat chow'     , 300.0) ,
    ('Mr. Squeeky' , 'swiss cheese' , 450.0) ,
    ('Pinky'       , 'rat chow'     , 320.0) ,
    ('Fluffball'    , 'swiss cheese' , 500.0)

Here is a high-level strategy:

Higher-order functions let us work at higher levels of conceptual abstraction so that we can write complex programs concisely, with minimal bureaucratic overhead.

The Accumulator Pattern

In the accumulator patten for processing a list:

Reducing Lists

We can turn the accumulator pattern into a higher-order function for processing lists:

Conceptually, the reduce function is doing this:

As with map and filter, reduce becomes even more useful when we curry it:

Examples: List Reduction

Now we can do list reductions with no loops and no recursion, often as one-liners:

This style of programming takes a little getting used to, but is very powerful.

Functions are the foundations of programming. I encourage you to be curious about how you can use them to express your ideas as programs.

To Do This Week: