Closures and Higher-Order Functions
There has been a great deal of interest in
closures lately, driven in great part by the fact that there is talk of adding some form of anonymous functions to the Java. Most of the time, people talk about “adding closures” to Java, and that prompts a flurry of questions of the form “what is a closure and why should I care?”
The discussion around closures tends to go on and on about the “closing over” of free variables and only lightly touch on the biggest change to Java: functions as first-class objects with a lightweight syntax for creating them. Making it easy to do something basic like define a new function is more than just a little syntactic sugar: it makes it easy to do new things with functions that were impractical when you needed a lot of boilerplate to make anything work.
Without understanding functional programming, you can’t invent MapReduce, the algorithm that makes Google so massively scalable.
I’m going to try to explain first class functions using Ruby (it
is possible to write code that does exactly the same thing using the current Java feature set, however the result is so wordy that it obscures the basic idea being presented: call it accidental complexity, or perhaps
yellow code.)
Ruby is a good language for demonstrating features that ought to be in Java. Like Java, Ruby uses squiggly brace syntax. Like Java, everything in Ruby is an object—whoops, Java has primitives. Okay, like Java, functions are represented as objects.
In Java you write:
interface IFromIAndI {
Integer call(Integer a, Integer b);
}
IFromIAndI add_two_integers = new IFromIAndI() {
public Integer call(final Integer a, final Integer b) {
return a + b;
}
};
(The Java convention is to name things in
lowerCamelCase, but we’ll ignore that. If you need to print this essay on a dot-matrix printer you may want to make some changes first.)
In Ruby you write the function as:
add_two_integers = lambda { |a,b| a + b }
Later on, when you want to call your function in Java, you write:
add_two_integers.call(35, 42);
And if you like semicolons, you write the
exact same thing in Ruby:
add_two_integers.call(35, 42);
You can do the same thing with multiplication:
multiply_two_integers = lambda { |a,b| a * b }
First Class FunctionsIn the examples above, functions look a little like methods. The Java version is obviously implemented as a method. But what we did in both cases was assign the resulting function to a variable. In Java, assigning a method to a variable is not particularly easy (it is possible using reflection).
Anything that can be assigned to a variable is a
value. If it can also be passed as a parameter or returned from a method (or function), we say it is a
first class value. Functions as first class values, or first class functions, are very interesting. For example, what can we do passing a function as a parameter to another function?
Hmmm. Well, I am breaking a cardinal rule of selling something. We’re talking about shiny new toys without identifying a problem to be solved. Let’s talk about my favourite problem: writing the same thing more than once, violating the
DRY principle.
Here are two pieces of similar Ruby code:
adder_wth_acc = lambda { |acc, list|
if list.empty?
acc
else
adder_wth_acc.call(acc + list.first, list[1..-1]) # [1..-1] returns a copy of the list without the first element
end
}
adder = lambda { |list|
adder_wth_acc.call(0, list)
}
adder.call([1, 2, 3, 4, 5])
And:
multiplier_with_acc = lambda { |acc, list|
if list.empty?
acc
else
multiplier_with_acc.call(acc * list.first, list[1..-1]) # [1..-1] returns a copy of the list without the first element
end
}
multiplier = lambda { |list|
multiplier_with_acc.call(1, list)
}
multiplier.call([1, 2, 3, 4, 5])
What do they both do? Pretty much the same thing: they accumulate the result of some binary operation over a list of values.
adder
accumulates addition, and
multiplier
accumulates multiplication. You could call this a “Design Pattern.” If you did that, you would use the exact chunk of code everywhere. I would call that retrograde. Didn’t our predecessors invent the subroutine so we could eliminate writing the exact same piece of code over and over again?
Why can’t we do the same thing? Well, we can. A subroutine does the same thing over and over again, but it takes different parameters as it goes. What is different between adder and multiplier? Ah yes, the adding and multiplying. Functions. What we want is a function that takes a function as a parameter.
Well, we said that with first-class functions, functions are values and can be passed as parameters. Let’s try it:
folder = lambda { |default_value, binary_function, list|
fold_with_acc = lambda { |acc, list|
if list.empty?
acc
else
fold_with_acc.call(binary_function.call(acc, list.first), list[1..-1])
end
}
fold_with_acc.call(default_value, list)
}
Now we can use our function that takes functions as a parameter:
folder.call(0, add_two_integers, [1, 2, 3, 4, 5])
folder.call(1, multiply_two_integers, [1, 2, 3, 4, 5])
This is
much better. When functions can take functions as parameters, we can build abstractions like
folder
and save ourselves a lot of code. Note that this would be a lot harder to read if we had to surround all of our functions with Object boilerplate in Java. That’s one of the key reasons why ‘syntactic sugar’—making it brief—is a big win.
And you know what? Functions are values, not just variables that happen to hold functions. These work just as well:
folder.call(0, lambda { |a, b| a + b }, [1, 2, 3, 4, 5])
folder.call(1, lambda { |a, b| a * b }, [1, 2, 3, 4, 5])
There’s just one problem (actually two, but I’m saving one for later): everywhere you use our new
folder
function, you need to remember that
add_two_integers
needs a default value of zero, but
multiply_two_integers
needs a default value of one. That’s bad. Sooner or later you will get this wrong.
What we need is a way to call
folder
without having to always remember the correct initial value. Should we extend our understanding of a function to include a default initial value for folding? If we’re thinking in Java, maybe our
IFromIAndI
interface needs a
getDefaultFoldValue
? I think not. Why should a function know anything about how it’s used? And besides, as we build other abstractions out of functions we’ll need more stuff.
If we aren’t careful, we’ll end up implementing the
Visitor
pattern on functions, and all of our brevity will go out the window. No, what we want is this: in one place we define that folding addition starts with a default value of zero and in another place we say we want to fold, say,
[1, 2, 3, 4, 5]
with addition. Then when we want to fold something else with addition, like
[2, 4, 6, 8, 10]
, we shouldn’t have to say anything about zero again.
Adding CurryWhat we need is a function that folds addition. Didn’t we say that functions are values that can be returned from functions? How about a function that makes a folding function? We should pass it our initial value and our binary function, and it should return a function that performs the fold without needing an initial value as a parameter:
fold_coder = lambda { |default_value, binary_function|
fold_with_acc = lambda { |acc, list|
if list.empty?
acc
else
fold_with_acc.call(binary_function.call(acc, list.first), list[1..-1])
end
}
lambda { |list|
fold_with_acc.call(default_value, list)
}
}
Now we can do the following:
adder = fold_coder.call(0, lambda { |a, b| a + b })
adder.call([1, 2, 3, 4, 5])
adder.call([2, 4, 6, 8, 10])
No more remembering that addition starts with a default of zero.
Actually, there’s a far simpler way to avoid having to remember the default value when you want to fold over addition. But let’s just play along so that we don’t have to come up with an entirely new set of examples to demonstrate the value of functions as first-class values.
Functional programmers (as opposed to the rest of us
dysfunctional programmers) will recognize this as
currying our
folder
function. Currying is when a function takes more than one parameter and you combine one of the parameters and the function to produce a function that takes fewer parameters.
Here’s a currying function in Ruby:
curry = lambda { |fn,*a|
lambda { |*b|
fn.call(*(a + b))
}
}
(
This is an improvement on an earlier version, thanks to Justin's comment.)
So you can use our new function to create an increment function out of our adder and a treble function out of our multiplier:
plus_one = curry.call(add_two_integers, 1)
times_three = curry.call(multiply_two_integers, 3)
If you are ever asked, “what good is currying?,” I hope I’ve given you an example you can use to explain why currying matters, and why people do it all the time (possibly without explicitly naming it). Although it doesn’t look like much when looking at trivial examples like functions that multiply by three, it’s much more useful when creating folders and mappers where you want some of the parameters to remain constant.
CompositionOur examples combined functions and non-functions to create new functions. Here’s an example from a recent post,
Don’t Overthink FizzBuzz, where I give a method for
composing two functions. The idea is that if you have multiple functions that each take one argument, you can combine them using
compose
. I also have a method that generates functions,
carbnation
:
# fizzbuzz.rb
def compose *lambdas
if lambdas.empty?
lambda { nil }
elsif lambdas.size == 1
lambdas.first
else
lambda do |n|
lambdas.first.call(compose(*lambdas[1..-1]).call(n))
end
end
end
def carbonation(modulus, printable_form)
i = 0
lambda do |n|
(i = (i + 1) % modulus) == 0 && printable_form || n
end
end
print(((1..100).map &compose( carbonation(15, 'FizzBuzz'), carbonation(5, 'Buzz'), carbonation(3, 'Fizz'))).join(' '))
The simple explanation of how it works is that
carbonation
generates functions that replace every so many elements of a list with a printable string.
Compose
composes any two or more methods together. So if you want to print out 100 numbers, but replace every third number with “Fizz,” every fifth with “Buzz,” and all those that are third
and fifth with “FizzBuzz,” you generate a function for each replacement, compose them together with compose, and then map the numbers from one to one hundred to the resulting überfunction.
When you look at this today, it seems weird and unreadable by Java standards. I wonder if adding first-class functions with simple syntax to Java will lead the Java community to a place where code like this will not appear out of place?
Just one more thingSo we started by saying that people are getting hung up on what makes a closure a closure, and there has been less emphasis on the benefits of using functions as first-class values. Did you notice that our
folder
function actually includes a non-trivial closure?
If you look at the
fold_with_acc
function, it makes use of
binary_function
, a variable from its enclosing lexical scope. This is not possible with the current version of Java: if you translate this to Java, when you make
fold_with_acc
and anonymous inner class, you will have to copy
binary_function
into a final member to use it. It simply won’t compile if you try an idiom-for-idiom translation, even adding explicit types.
And then if you look at the anonymous function it returns,
lambda { |list| fold_with_acc.call(default_value, list) }
, that anonymous function uses
default_value
,another variable from the enclosing lexical scope. Once again you will have to fool around with final variables to make this work, or perhaps declare full-fledged object with constructors.
(If you try writing this simple example out in Java, you quickly find yourself inventing a lot of classes or interfaces. And they have some complicated types, like a function taking an integer and a function taking two integers, returning a function taking a list of integers and returning an integer.
After twenty minutes of that, you understand why the
ML and
Haskell communities use
type inference: If the types are that complicated, it’s incredibly helpful to have the compiler check them for you. Yet if the types are that verbose, it’s incredibly painful to write them out by hand. Even if your IDE were to write them for you, they take up half the code, obscuring the meaning.
You also get why the Ruby on Rails community doesn’t care about type checking: types for CRUD applications are way less complicated than types for first-class functional programs.)
That’s InterestingPart of the interest in closures is in simplifying the syntax around functions, and part of the interest is in the way that access to enclosing scope would simplify a lot of code. There’s a whole debate around the value of simplification in a world where all serious languages are
Turing Equivalent.
I hope you’re convinced, by now, that programming languages with first-class functions let you find more opportunities for abstraction, which means your code is smaller, tighter, more reusable, and more scalable.
For me, simpler is just nicer until something reaches a certain tipping point: when it becomes so simple that the accidental complexity of using it goes away, I will start using it without thinking about it. Tail optimization is like that: as long as recursion is slower than iteration and sometimes breaks, I have to think about it too much. But when I’m not burdened with “except…” and “when performance is not a factor…” it becomes natural.
And then something interesting happens. It changes the way I look at problems, and one day I see a whole new way to do something that I never saw before. Functions as first class values are definitely one of those things that change everything.
Further Reading
If this has whet your appetite for more,
Structure and Interpretation of Computer Programs is
the book on higher-order functions and how they can be used as building blocks to create more elaborate abstractions such as object-oriented programming.
The Seasoned Schemer devotes an entire book to the uses of functions. Although the examples are in Scheme, the language is dead simple to learn and the techniques in the book can be applied to Ruby and Java (or at least to a future version of Java where you do not need functors).
The second edition of
Programming Ruby is an indispensable guide. Even if you will not be using Ruby immediately, pick it up and discover why so many people are lauding the language's simple, clean design and powerful Lisp-like underpinnings.
As the author says, “
Higher-Order Perl: Transforming Programs with Programs is about functional programming techniques in Perl. It’s about how to write functions that can modify and manufacture other functions.
“Why would you want to do that? Because that way your code is more flexible and more reusable. Instead of writing ten similar functions, you write a general pattern or framework that can generate the functions you want; then you generate just the functions you need according to the pattern. The program doesn’t need to know in advance which functions are necessary; it can generate them as needed. Instead of writing the complete program yourself, you get the computer to write it for you.”
It’s worth reading even if you have no intention of using Perl: the ideas span languages, just as SICP is worth reading even if you don’t use Scheme at work. And be sure to read
Higher-Order JavaScript and
Higher-Order Ruby. They translate HOJ’s ideas to other languages.
Notable Follow-ups:Some useful closures, in Ruby: “The
Dylan programming language included four very useful functions for working with closures:
complement,
conjoin,
disjoin and
compose. The names are a bit obscure, but they can each be written in a few lines of Ruby.”
From Functional to Object-Oriented Programming: “OO allows a traceable connection between the conceptual design level and the implementation level. Concepts have names, so you can talk about them, between programmers and architects.”
HOF or OOP? Yes!: “First-class functions are a natural fit with OO, as evidenced by their presence in OO languages that aren’t glorified PDP-11 assemblers with some OO stuff bolted on the side.”
But Y would I want to do a thing like this?: “To truly learn a new tool, you must not just learn the new tool, you must apply it to new kinds of problems. It’s no good learning to replace simple iteration with first-class functions: all you’ve learned is syntax. To really learn first-class functions, you must seek out problems that aren’t easily solved with iteration.”
Labels: java, lispy, popular, ruby