Functional Programming for the Object-Orie - Brian_Marick
Functional Programming for the Object-Orie - Brian_Marick
Oriented Programmer
Brian Marick
* * * * *
This is a Leanpub book. Leanpub empowers authors and publishers with the
Lean Publishing process. Lean Publishing is the act of publishing an in-
progress ebook using lightweight tools and many iterations to get reader
feedback, pivot until you have the right book and build traction once you
do.
* * * * *
III Coda
VI Glossary
Introduction
Many, many of the legendary programmers know many programming
languages. What they know from one language helps them write better code
in another one. But it’s not really the language that matters: adding
knowledge of C# to your knowledge of Java doesn’t make you much better.
Those languages are too similar: they encourage you to look at problems in
pretty much the same way. You need to know languages that conceptualize
both problems and solutions in substantially different ways.
The functional programming style is nicely different from the OO style, but
there are many interesting points of comparison between them. This book
aims to teach you key elements of the functional style, helping you take
them back to your OO programming.
There’s a bit more, though: although the functional style has been around
for many years, it’s recently become trendy, partly because language
implementations keep improving, and partly because functional languages
are better suited for the problem of running one program on multiple cores.
Some trends with a lot of excitement behind them wither, but others (like
object-oriented programming) succeed immensely. If the functional style
becomes commonplace, this book will position you to be around the leading
edge of that wave.
There are many functional languages. There are arguments for learning the
purest of them (Haskell, probably). But it’s also worthwhile to learn a
slightly-less-pure language if there are more jobs available for it or more
opportunities to fold it into your existing projects. According that standard,
Clojure and Scala–both of which piggyback on the Java runtime–stand out.
This book will use Clojure.
Prerequisites
You need to know at least one object-oriented programming language.
Anything will do: Objective-C, C++, C#, Java, Ruby, Python: you name it.
Throughout the book, I will have teased you with what seems to be
deliberately and ridiculously inefficient code. In the next chapter, I’ll
show how that apparent inefficiency is an illusion. In reality, we’re
relying on the runtime to make two kinds of optimizations for us:
Lazy evaluation: In functional languages, it’s common for some or all
values to be lazy, meaning that they (and their component parts) are
only calculated when demanded. I’ll show how this collapses what
appear to be many loops into just one. More importantly, I’ll show
how it allows you to let free of the idea that you must be able to
calculate in advance how much data you’ll need. Instead, you can just
ask for all of it and let the runtime avoid generating too much.
Sharing structure behind the scenes: While you can’t mutate functional
data structures, the language runtime can. I’ll show an example of how
the runtime can optimize away the wasteful copying your program
appears to be doing. I’ll also discuss the implications of adopting
immutability in object-oriented programs.
When you start looking at data in terms of its shape, it begins to seem
reasonable to have functions decide what code to run based not on
explicit if tests but rather on matching patterns against shapes.
Generic functions support a verb-centered way of thinking about the
world: there are actions that can apply very broadly. The specifics of
an action depend on some properties (determined at runtime) of the
values it’s applied to. Generic functions are the flip side of the noun-
centered approach taken by object-oriented languages.
That finishes the book, except for the optional parts on object models and
monads.
You can find exercise solutions in this book’s Github repository. If you use
the Git version management tool, you can fetch the code like this:
1 git clone git://github.com/marick/fp-oo.git
You can also use your browser to download Zip or Tar files.
At last resort, you can browse the repository through your browser. The
exercise descriptions include a clickable link to the solutions.
The repository also includes some code that you’ll load before starting
some of the exercises. The instructions for loading are given later.
Testing
I’m a big fan of test-driven design and testing in general, so I’ve written
tests for the presupplied code and my exercise solutions. If you’re interested
in how to test Clojure code, you can find those tests also on Github.
These tests use my own testing library, Midje. With it, you can run my tests
like this:
1 709 $ lein midje
2 ##Many lines of output, normally something I hate
3 ##to see from tests. They appear in this case because
4 ##my solution files print the results of examples when
5 ##they're loaded.
6 All claimed facts (1117) have been confirmed.
7 710 $
I chose this painting because I’m struck by the way the woman (Monet’s
wife) is looking down on us with something of a haughty expression. We’ve
surprised her—interrupted her—and we need to give an account of
ourselves.
This “prove you’re worthy of my attention” attitude has been, I’m sorry to
say, all too often characteristic of functional programmers’ interactions with
the rest of the programming world. Although that’s changing, it contributes
to functional programming’s reputation as forbiddingly rigid, mathematical,
and unforgiving of human limitations.
I want this book to be the opposite: informal yet serious, viewing programs
as code to be grown rather than mathematical objects to contemplate. That’s
the reason I went looking for an Impressionist painting for the cover. The
loose brush strokes, the non-rigid edges, the fascination with variety over
formal choices—even the embrace of awkwardness (I really don’t like the
darker blue squiggles on the left side of the cloud): all these things fit my
style of explanation.
About links
In the PDF version, links within a book appear as colored text, and links to
external sites appear in footnotes. Both are clickable.
In the Kindle version, all links appear inline. That makes tasks like
installing Clojure or doing exercises awkward: you can’t easily read the
URLs on the Kindle and type them on your computer. (You have to actually
follow the links on the Kindle to see the URLs.) I recommend getting both
the Kindle and PDF versions. Use the latter when you need to work with
links. (That should only be in exercises, which you can’t do on the Kindle
anyway.)
There is a glossary
I never notice a book has a glossary until I turn the last page of the last
chapter and discover it. If you’re like me, you’ll be glad to read now that
there’s a glossary. (However, if you’re like me, you also skim introductions
and so will miss this paragraph.)
Getting help
There’s a mailing list.
Notes to reviewers
You can put your comments on the mailing list or by filing issues on
Github. It doesn’t matter to me. Comments that would benefit from group
discussion are better sent to the mailing list.
Please tag your comments with the version of the book to which they apply.
The version you’re reading now is garrulous gastropod.
fastidious flounder
ecstatic earthworm
Three new chapters (12, 13, and 14): The zipper data structure as an
example of working around the constraint of immutability, the
implications of lazy evaluation, and pattern matching.
Added material about using Light Table as an alternative to Leiningen.
Miscellaneous fixes to the text.
discursive diplodocus
crafty chameleon
bashful barracuda
Acknowledgments
I thank these people for commenting on the mailing list and filing bug
reports:
Adrian Mowat,
Aidy Lewis,
Ben Moss,
Chris Pearson,
Greg Spurrier,
Jim Cooper,
Juan Manuel Gimeno Illa,
Julian Gamble,
Matt Mower,
Meza,
Mike Suarez,
Oliver Friedrich,
Ondrej Beluský,
Robert D. Pitts,
Robert “Uncle Bob” Martin,
Roberto Mannai,
Stephen Kitt,
Suvash Thapaliya,
Ulrik Sandberg,
and
Wouter Hibma.
And for other help, thanks to Dawn Marick, John MacIntyre, Paul Marick,
and Sophie Marick.
Advertisement
I would be happy to do Ruby or Clojure contract programming or
consulting for you. I’m also competent to coach teams on working in the
Agile style.
Light Table
One simple way to install Clojure is to install the Light Table playground
instead. It is something like an IDE for Clojure, with the interesting
property that it evaluates expressions as you type them. You can find out
more at Chris Granger’s site.
You’ll work in something Light Table calls the “Instarepl”. That’s its
version of the Clojure read-eval-print loop (typically called “the repl”). You
type text in the left half of the window, and the result appears in the right. It
looks like this:
Light Table
The book doesn’t show input and output in the same split screen style.
Instead, it shows input preceded by a prompt, and output starting on the
next line:
1 user=> (if true 5)
2 5
Important: as of this writing, Light Table does not automatically include all
the normal repl functions. You have to manually include them with this
magic incantation:
1 (use 'clojure.repl)
Leiningen
If you want to run Clojure from the command line, first install Leiningen.
Go to its page and follow the installation instructions.
When you’re finished, you can type the following to your command line:
1 lein repl
That asks Leiningen to start the read-eval-print loop (typically called “the
repl”). Don’t be alarmed if nothing happens for a few seconds: because the
Java Virtual Machine is slow to start, Clojure is too.
From now on, I won’t use screen shots to show repl input and output.
Instead, I’ll show it like this:
1 user=> (if true 5)
2 5
The most important thing you need to know now is how to get out of the
repl. That’s done like this:
1 user=> (exit)
Emacs: clojure-mode
Vim: one is VimClojure
You can copy and paste Clojure text into the repl. It handles multiple lines
just fine.
Many people like to follow along with the book by copying lines from the
PDF version and pasting them into the repl. That’s usually safe. However,
my experience is that such text is sometimes (but not always!) missing
some newlines. That can cause perplexing errors when it results in source
code ending up on the same line as a to-the-end-of-the-line comment. In
later chapters (where there are more lines with comments), I’ll provide text
files you can paste from.
If you want to use a Clojure command to load a file like (for example)
solutions/add-and-make.clj, use this:
More is happening than just echoing the input to output, though. This
profound calculation requires three steps.
First, the reader does the usual parser thing: it separates the input into
discrete tokens. In this case, there’s one token: the string "1". The reader
knows what numbers look like, so it produces the number 1.
The reader passes its result to the evaluator. The evaluator knows that
numbers evaluate to themselves, so it does nothing.
The evaluator passes its result to the printer. The printer knows how to print
numbers, so it does.
Strings, truth values, and a number of other types play the same self-
evaluation game:
1 user=> "hi mom!"
2 "hi mom!"
3
4 user=> true
5 true
*file* is a symbol, which plays roughly the same role in Clojure that
identifiers do in other languages. The asterisks have no special significance:
Clojure allows a wider variety of names than most languages. Most
importantly, Clojure allows dashes in symbols, so Clojure programmers
prefer them to underscores. StudlyCaps or interCaps style is uncommon in
Clojure.
Let’s step through the read-eval-print loop for this example. The reader
constructs the symbol from its input characters. It gives that symbol to the
evaluator. The evaluator knows that symbols do not evaluate to themselves.
Instead, they are associated with (or bound to) a value. *file* is bound to
the name of the file being processed or to "NO_SOURCE_PATH" when we’re
working at the repl.
Now let’s walk through what happens when you ask the repl to add one and
two to get three:
1 user> (+ 1 2)
2 3
In this case, the first token is a parenthesis, which tells the reader to start a
list, which I’ll represent like this:
The second token represents the symbol +. It’s put in the list:
The next two tokens represent numbers, and they are added to the list. The
closing parenthesis signals that the list is complete:
The reader’s job is now done, and it gives the list to the evaluator. Lists are
a case we haven’t described before. The evaluator handles them in two
steps:
To emphasize how seriously the evaluator expects the first element of the
list to be a function, here’s what happens if it’s not:
1 user=> (1 2 3)
2 java.lang.ClassCastException: java.lang.Integer cannot be cast to
3 clojure.lang.IFn (NO_SOURCE_FILE:0)
(I’m afraid that Clojure error messages are sometimes not as clear as they
might be.)
1.4 A note on terminology: “value”
Since Clojure is implemented on top of the Java runtime, things like
functions and numbers are Java objects. I’m going to call them values,
though. In a book making distinctions between object-oriented and
functional programming, using the word “object” in both contexts would be
confusing. Moreover, Clojure values are (usually) used very differently than
Java objects.
There are other predicates that let you ask questions about what kind of
value a value is:
1 user> (number? 1)
2 true
To add a little visual interest, let’s personify the evaluator as a bird. That’s
appropriate because parent birds take in food, process it a little, then feed
the result to their babies. The evaluator takes in data structures from the
reader, processes them, and feeds the result to the printer. Here’s a picture
of the evaluator at taking in a list:
A list is a compound data structure, so (in this case) three evaluators are set
to work:
The bottom two have an easy job: numbers are already digested (evaluate to
themselves), so nothing need be done. The top bird must convert the
symbol into the function it names.
Each of these sub-evaluators feeds its result to the original evaluator, which
substitutes those values for the originals, making a list that is almost–but
not quite–the same:
“Too complicated!” it thinks, and recruits three fellows for the three list
elements:
The first two are happy with what they’ve been fed, but the last one has
gotten another list, so it recruits three more fellows:
When the third-level birds finish, the lazy second-level bird substitutes the
values they provide, so it now has this list:
When all the second-level birds are done, they feed their values to the top-
level evaluator:
The top-level evaluator substitutes them in:
It applies the function to the two arguments, yielding 3, which it feeds to the
printer:
1.7 Making functions
Suppose you type this:
1 user> (fn [n] (+ n n))
As another list headed by a symbol, this looks something like the function
applications we’ve seen before. However, fn is a special symbol, handled
specially by any evaluator. There are a smallish number of special symbols
in Clojure. The expressions headed by a special symbol are called special
forms.
In the case of this special form, the evaluator doesn’t recruit a flock to
handle the individual elements. Instead, it conjures up a new function. In
this case, that function takes a single parameter1, n, and has as its body the
list (+ n n). Note that the parameter list is surrounded by square brackets,
not parentheses. (That makes it a bit easier to see the structure of a big
block of code.)
The functions you create are just as “real” as the functions that come pre-
supplied with Clojure. For example, they print just as helpfully:
1 user=> (fn [n] (+ n n))
2 #<user$eval66$fn__67 user$eval66$fn__67@5ad75c47>
(I’ve used the underlining to highlight the first position in the list.)
The whole thing is a list, so the top-level evaluator recruits two lower-level
evaluators. One evaluates a list headed by the symbol fn; the other
evaluates a number. When they hand their values to the top-level evaluator,
it substitutes, so it now has this:
Hey! Look! A list! We know how to handle a list. The list elements are
evaluated by sub-evaluators, and the resulting function (the + function
value) is applied to the resulting arguments. Were the + function value a
user-written function, it would also be evaluated by substitution. So would
be most of the Clojure library functions. There are some primitive
functions, though, that are evaluated by Java code. (It can’t be turtles all the
way down.)
Despite being tedious, this evaluation procedure has the virtue of being
simple. (Of course, the real Clojure compiler does all sorts of
optimizations.) But you may be thinking that it has the disadvantage that it
can’t possibly work. What if the code contained an assignment statement,
something like the following?
1 (fn [n]
2 (assign n (+ 1 n))
3 (+ n n))
“Still,” you might object, “what if the argument is some data structure that
the code modifies? If you pre-substitute the whole data structure wherever
the parameter appears, changes to the data structure in one part of the
function won’t be seen in other parts of the function!” Code suffering from
this problem would look something like this:
1 (fn [tree]
2 (replace-left-branch tree 5)
3 (duplicate-left-branch tree))
Because of substitution, the tree on the last line that’s having its left branch
duplicated is not the tree that had its left branch replaced on the previous
line.
Clojure avoids this problem by not allowing you to change trees, sets,
vectors, lists, hashmaps, strings, or anything at all except a few special
datatypes. In Clojure, you don’t modify a tree, you create an entirely new
tree containing the modifications. So the function above would look like
this:
1 (fn [tree]
2 (tree-with-duplicated-left-branch
3 (tree-with-replaced-left-branch tree 5)))
We’ll be discussing the details of all this in the chapter on immutability. For
now, ignore your efficiency concerns–“Duplicating a million-element
vector to change one element?!”–and delegate them to the language
implementor. Also hold off on thinking that programming without an
assignment statement has to be crazy hard–it’s part of this book’s job to
show you it’s not.
Since functions are values not essentially different than other values, you
might expect that you can give names to strings, numbers, and whatnot.
Indeed you can:
1 user> (def two 2)
2 #'user/two
3 user> (twice two)
4 4
You can use def with a particular symbol more than once. That’s the only
exception to Clojure’s “no changing a binding” rule2. It’s useful for
correcting mistakes:
1 user=> (def twice (fn [n] (- n n)))
2 user=> (twice 10)
3 0
4 user=> ;; Darn! (This, by the way, is a Clojure comment.)
5 user=> (def twice (fn [n] (+ n n)))
6 user=> (twice 10)
7 20
1.10 Lists
We’ve seen that lists are surrounded by parentheses. The evaluator function
interprets a list as an excuse to apply a function to arguments. But lists are
also a useful data structure. How do you say that you want a list to be
treated as data, not as code? Like this:
1 user> '(1 2)
2 (1 2)
The quote tells the evaluator not to interpret the list as a function call. That
character is actually syntactic sugar for a more verbose notation:
1 user> (quote (1 2))
2 (1 2)
You can take apart lists. Here’s a way to get the first element:
1 user> (first '(1 2 3 4))
2 1
Exercise 1: Given what you know now, can you define a function second
that returns the second element of a list? That is, fill in the blank in this:
1 user> (def second (fn [list] ____))
Be sure to try your solution at the repl. (When you do, you’ll notice that
you’ve just overridden Clojure’s built-in second function. Don’t worry
about that.)
1.11 Vectors
Lists are (roughly) the classic linked list that many of us encountered when
we first learned programming. That means code has to traverse the whole
list to get to the last element. Clojure’s creator cares about efficiency, so
Clojure also makes it easy to use vectors, where it takes no more time to
access the last element than the first.
Note that I didn’t have to quote the vector to prevent the evaluator from
trying to use the value of 1 as a function. That only happens with lists.
The first, rest, and nth functions also work with vectors. Indeed, most
functions that apply to lists also apply to vectors.
There’s a third datatype called the lazyseq (for “lazy sequence”) that’s also
sequential. That datatype won’t be relevant until we discuss laziness. I
mention it because some functions that you might think produce vectors
actually produce lazyseqs. For example, consider this:
1 user=> (rest [1 2 3])
2 (2 3)
The first time I typed something like that, I expected the result to be the
vector [2 3], and the parentheses confused me. The result of rest is a
lazyseq, which prints the same way as a list. Here’s how you can tell the
difference:
1 user=> (list? (rest [1 2 3]))
2 false
3 user=> (seq? (rest [1 2 3]))
4 true
5 user=> (vector? (rest [1 2 3]))
6 false
Such changes of type seem like they’d lead to bugs. In fact, the differences
almost never matter. For example, equality doesn’t depend on the type of a
sequential data structure, only on the contents. Therefore:
1 user=> (= [2 3] '(2 3))
2 true
3 user=> (= [2 3] (rest [1 2 3]))
4 true
The single most obvious difference between a list and vector is that you
have to quote lists.
It will never matter in this book whether you create a list or vector, so suit
your fancy. I will often use “sequence” from now on when the difference is
irrelevant.
seqs
The predicate seq? doesn’t actually check specifically for a lazyseq. It responds true for
both lists and lazyseqs, and the word seq is used as an umbrella term for both types. If
you really need to know the complete set of sequential types and the names that refer to
them, see the table below. However, the definition of a seq will never matter for this
book.
That means that literal vectors can (and often do) contain code snippets:
1 user=> [ (+ 1 1) (- 1 1) ]
2 [2 0]
It also means quoting is sometimes required for vectors as well as lists. Can
you guess the results of these two code snippets?
[inc dec]
'[inc dec]
Quoting doesn’t only apply to entire lists and vectors. You can quote
symbols:
1 user=> 'a
2 a
1.14 Conditionals
Despite the anti-if campaign, the conditional statement is one of primordial
operations of the Turing Machine (that is, computer). Conditionals in
Clojure look like this:
1 user=> (if (odd? 3)
2 (prn "Odd!")
3 (prn "Even!"))
4 "Odd!"
5 nil
The prn function prints to the output. Unlike in some languages, ifs are
expressions that produce values and can be embedded within other
expressions. The value of an if expression is the value of the “then” or
“else” case (whichever is chosen). Since prn always returns the value nil,
that’s what the repl printed in the example above. (nil is called null in
some other languages–it’s the object that is no object, or the pointer that
points to nothing, or what Tony Hoare called his billion-dollar mistake.)
1.15 Rest arguments
Clojure functions can take a variable number of arguments:
1 user> (add-squares 1 2)
2 5
3 user> (add-squares 1 2 3)
4 14
That function gathers all the arguments into the list args, which it then
returns.
Now that we know how to define rest arguments, here’s what add-squares’
definition would look like:
1 (def add-squares
2 (fn [& numbers]
3 (...something... numbers)))
What could the “something” be? The next section gives us a clue.
That is, we want to somehow turn that vector into the same value as this +
expression:
1 user=> (+ 1 4 9 16)
2 30
(Notice that + can take any number of arguments.)
What we need is some function that hands all the elements of the vector to +
as if they were arguments directly following it in a list. Here’s that function:
1 user=> (apply + [1 4 9 16])
2 30
1. cons (the name chosen more than 50 years ago as an abbreviation for
the verb “construct”) produces a list3 whose first element is cons’s
first argument and whose rest is cons’s second argument:
1 user=> (cons "the first element" [1 2 3])
2 ("the first element" 1 2 3)
(Notice that cons, like rest earlier, takes a vector in but doesn’t
produce one.)
2. eval is our old friend the bird-like evaluator. In my-apply, it’s been
given a list headed by a function, so it knows to apply the function to
the arguments.
After that, it is evaluated “from the inside out”, each result being substituted
into an enclosing expression, finally yielding 6.4
1.17 Loops
How do you write loops in Clojure?
Instead, like Ruby and other languages, Clojure encourages the use of
functions that are applied to all elements of a sequence. For example, if you
want to find all the odd numbers in a sequence, you’d write something like
this:
1 user> (filter odd? [1 2 3 4])
2 (1 3)
The filter function applies its first argument, which should be a function,
to each element of its second argument. Only those that “pass” are included
in the output.
Question: How would you find the first odd element of a list?
Answer:
1 user> (first (filter odd? [1 2 3 4]))
2 1
Question: Isn’t that grossly inefficient? After all, filter produces a whole
list of odd numbers, but you only want the first one. Isn’t the work of
producing the rest a big fat waste of time?
Answer: No. But you’ll have to read the later discussion of laziness to find
out why.
The map function is perhaps the most common loop-like function. (If you
know Ruby, it’s the same as collect.) It applies its first argument (a
function) to each element of a sequence and produces a sequence of the
results. For example, Clojure has an inc function that returns one plus its
argument. So if you want to increment a whole sequence of numbers, you’d
do this:
1 user> (map inc [0 1 2 3])
2 (1 2 3 4)
The map function can take more than one sequence argument. Consider this:
1 user> (map * [0 1 2 3]
2 [100 200 300 400])
3 (0 200 600 1200)
You’ll probably need other Clojure functions to solve the problems you put
to yourself. Therefore, I also describe some of them below.
Functions to try
take
distinct
concat
repeat
interleave
drop and drop-last
flatten
partition only the [n coll] case, like: (partition 2 [1 2 3 4])
every?
remove and create the function argument with fn
Other functions
(= a b) – Equality
(count sequence) – Length
and, or, not – Boolean functions
(cons elt sequence) – Make a new sequence with the elt on the
front
inc, dec – Add and subtract one
(empty? sequence) – Is the sequence empty?
(nth sequence index)– Uses zero-based indexing
(last sequence) – The last element
(reverse sequence) – Reverses a sequence
print, println, prn – Print things. print and println print in a
human-friendly format. For example, strings are printed without
quotes. prn prints values in the literal format you’d type to create
them. For example, strings are printed with double quotes. println
and prn add a trailing newline; print does not. All of these can take
more than one argument.
pprint – Short for “pretty print”, it prints a nicely formatted
representation of its single argument.
Note: if the problems you think of are anything like the ones I think of,
you’ll want to use the same sequence in more than one place, which would
lead to annoying duplication. Since you don’t know how to create local
variables yet, the easiest way to avoid duplication is to create and call a
function:
1 (def solver
2 (fn [x]
3 (... x ... ... ... ... x ... ...)))
4
5 (solver [1 2 3 4 5 6 7])
This one is tricky. My solution is very much in the functional style, in that it
depends on sequences being easy to create and work with. So I’ll provide
some hints. Here and hereafter, I encourage you to try to finish without
using the hints, but not to the point where you get frustrated. Programming
is supposed to be fun.
Hint: map can take more than one sequence. If you give it two sequences, it
passes the first of each to its function, then the second of each, and so on.
Exercise 8: In the first exercise in the chapter, I asked you to complete this
function:
1 (def second (fn [list] ____))
Notice that list is a parameter to the function. We also know that list is
(globally), a function in its own right. That raises an interesting question.
What is the result of using the following function?
1 user=> (def puzzle (fn [list] (list list)))
2 user=> (puzzle '(1 2 3))
3 ????
In this part of the book, we’ll use Clojure to implement an object system
patterned after Java’s. (In the optional Part V, we’ll extend it to be more
like Ruby’s.)
Before we start, though, it’s important for us to agree on what words mean.
An instance is synonymous with “object”, but it’s often used to emphasize
that the object has been instantiated from a class, whose role is to describe
(in some sense) a lot of similar objects.
Instances contain instance variables, which are named bits of data. These
variables may or may not be defined by the class. (In Java, they are. In
Ruby, they are not.)
Sometime in the early 1980’s, I introduced Ralph Johnson to the head of the
Research Division of a computer manufacturer that doesn’t exist any more.
This was in the days when Smalltalk was the object-oriented language, and
Ralph was an expert in Smalltalk. At one point during the discussion, the
division head said, “We were doing object-oriented programming 20 years
ago! In assembly language!” That comment was both:
true, and
irrelevant.
The Python method has a self parameter, whereas the Java one appears not
to. Python is being more honest (though less convenient) than Java. Every
Java method really does have a self parameter (though Java calls it this).
But since that parameter must always be there, the language designers
decided to make it implicit.
The Java designers also decided to change the syntax of functions to single
out the first argument. In the old-fashioned math you learned at school,
functions take their arguments at the right:
1 shift(point, xinc, yinc)
2 reflect-across-origin(point)
3 ...
Even Python uses this syntax. But, somewhere behind the scenes, that
format is converted into the old-fashioned format (so that the self
argument is in the right place).
The first definition makes the instance variables seem like very distinct
things within the broad scope of the class. The second hints at what they
really are: a single data structure that contains a mapping (or dictionary)
from names to values.
3.1 Maps
In Clojure, the map is the data structure of choice for connecting names to
values. It’s like Java’s HashMaps, Ruby’s Hashes, or Python and
Smalltalk’s Dictionaries. Maps have this literal notation:
1 user> {:a 1, "b" 2}
2 {:a 1, "b" 2}
The first, third, and following odd-numbered elements are the keys.
They can be any type of object, but it’s very common to use colon-
prefixed keywords because, unlike symbols, they’re self-evaluating
(don’t need to be quoted).
The second, fourth, and following even-numbered elements are the
values. They can also be of any type (including nested maps).
It’s common to use commas to separate key-value pairs. In Clojure,
commas are white space: the reader ignores them.
If you try to fetch the value of a key that’s not in the map, you get nil:
1 user=> (get {} :x)
2 nil
get is actually somewhat rare in Clojure code, though. Keywords have the
nice property that they act like functions when placed as the first element of
a list:
1 user> (:a {:a 1, :b 2})
2 1
Consider :a in the above to mean “get the value using me as the key”. In
this book, we’ll be using this style a lot, and we’ll often write functions like
this one:
1 (def do-something-to-map
2 (fn [function]
3 (function {:a "a value", :b "b value"})))
Question: How do assoc and merge handle duplicate keys? Try it and see!
1 user=> (assoc {:a 1} :a 2222)
2 ????
3 user=> (merge {:a 1, :b 2, :c 3} {:a 111, :b 222, :d 4} {:b "two"})
4 ???
If that was the first line of code you saw in this book, you could probably
guess it represented a mathematical point, but that’s only because you’ve
already seen a million examples of them. You’d have much more trouble
with other kinds of objects.
So let’s add the class. For the moment, it’ll just be used as documentation.
Because it’s a kind of metadata (data about data), I’ll give it a funny name:
1 (def Point
2 (fn [x y]
3 {:x x
4 :y y
5 :__class_symbol__ 'Point})) ;; <<==
From Clojure’s point of view, a keyword like :__class_symbol__ is no
different than any other. To us, though, this double-underscore convention
will signal that a keyword is part of the workings of the object system, not
something that ordinary client code would ever use. (In more polished
object systems, this kind of metadata is typically inaccessible to client
code.)
(I’m using class-of instead of class because Clojure already has a class
function.)
Given a large enough dose of magic mushrooms, we can hallucinate that the
class-of, x, and y callables are instance methods of Point. (It requires
mushrooms because we’re used to thinking of methods as being somehow
attached to objects, and these are free-floating. We’ll fix that in the next
chapter.)
Here’s a less boring method: shift. It takes a particular point and shifts it
according to an x-increment and a y-increment. Since Clojure doesn’t let us
modify maps, shift has to create a new Point.
1 (def shift
2 (fn [this xinc yinc]
3 (Point (+ (x this) xinc)
4 (+ (y this) yinc))))
5
6 user=> (shift (Point 1 200) -1 -200)
7 {:x 0, :y 0, :__class_symbol__ Point}
3.4 Exercises
You can find starting Clojure code in sources/add-and-make.clj. You can
either copy-and-paste the code into the repl, or you can type the following
to a repl started in the root directory of a cloned repository:
1 user=> (load-file "sources/add-and-make.clj")
Note that Clojure requires that you def any name (like a function name)
before you can use it. If you paste the definition of shift (which uses
Point) before the definition of Point, you’ll get this error:
Implement an add function that adds two points, producing a third. First
implement it without using shift. Then implement it using shift. (If you
think of add as an instance method, calling shift is like using another
instance method in the same class.)
Our Point function matches the way Python constructors are called. Java
would use new Point and Ruby would use Point.new. That suggests an
alternative syntax:
1 (new Point 3 5)
However, Clojure reserves new for the creation of Java objects, so we’ll use
make instead:
1 (make Point 1 2)
Write the function make, assuming that the function Point already exists.
The exercise source contains a Triangle constructor. Does your make work
with it? For example:
1 (make Triangle (make Point 1 2)
2 (make Point 1 3)
3 (make Point 3 1))
If not, make it work.
However, that approach is prone to typos2. You can use one of the sequence
functions you explored in the previous chapter to do a more concise job.
1. Vectors and maps are also callables, though we won’t use that fact in
this book. If you’re curious, try ({:a 1, :b 2} :a) and (["zero"
"one"] 1).↩
2. I once found a bug in a fairly complicated Unix kernel function. It had
three “inode” parameters: i, ii, and iii. Guess what the bug was?↩
4. All the Class in a Constructor
We don’t usually think of objects having their methods scattered all over the
global namespace the way shift and add are. We usually think of methods
as somehow belonging to objects:
That clearly won’t do. Let’s add a send-to function that looks more like a
message send in a regular object-oriented language. Like this:
1 user=> (send-to point :shift -2 -3)
2 {:x -1, :y -1, :__class_symbol__ Point, :__methods__ {...}}
Make sure you understand send-to, since we’ll be changing it for the rest
of this part of the book.
First, we’ll pull out the methods from the instance into a new Point object:
1 (def Point
2 ;; (fn [x y]
3 ;; {;; initializing instance variables
4 ;; :x x
5 ;; :y y
6 ;;
7 ;; ;; Metadata
8 ;; :__class_symbol__ 'Point
9 {
10 :__instance_methods__
11 {
12 :class :__class_symbol__
13 :shift
14 (fn [this xinc yinc]
15 (make Point (+ (:x this) xinc)
16 (+ (:y this) yinc)))
17 }
18 })
We have to find a replacement for what the commented-out code used to do.
We no longer have a Point constructor, but you’ve already implemented an
alternative notation for us to use:
1 user=> (make Point 1 2)
Your old implementation will have to be updated, though. Let’s pattern the
revised make after the three steps new performs in many object-oriented
languages:
5.1 Let
One way to introduce something like a local variable is to use nested
functions. Suppose we wanted to implement the following without the
duplication:
1 user=> (* (+ 1 2)
2 (+ 1 2)
3 5)
4 45
We could do this:
1 user=> ( (fn [name] (* name name 5)) (+ 1 2))
2 ___________________________
3 45
Notice that let expressions return a value. So you can nest them inside
other expressions:
1 user=> (+ 1 (let [one 1] (* one one)) 3)
2 _________________________
3 5
Notice that all the name-value pairs are surrounded by a single set of square
brackets. That’s like the way that brackets are used to hold function
parameters.
A hint
let expressions define a computation as a series of steps. If you find multi-step let
expressions confusing, execute each step at the repl. To show what I mean by that,
here’s a silly function that operates on a map like {:a 1}:
1 (fn [starting-map]
2 (let [bigger-map (assoc starting-map :b 2)
3 overridden-a (assoc bigger-map :a 33)]
4 overridden-a))
It’s annoying that Point is bound to a map that refers straight back to
Point, but we’re stuck with that. Languages with syntactic sugar for class
definitions hide such circularity from the programmer1.
Since the constructor returns the new map that is to be (hallucinated to be)
the object, that’s the return value of make.
All that given, the final implementation for make looks like this:
1 (def make
2 (fn [class & args]
3 (let [allocated {}
4 seeded (assoc allocated
5 :__class_symbol__ (:__own_symbol__ class))
6 constructor (:add-instance-values
7 (:__instance_methods__ class))]
8 (apply constructor seeded args))))
Message dispatch will be similar to the last part of make, but not identical:
We got the symbol SomeClass into the map by protecting it from evaluation
with quote. Since eval removes quotes, it follows that we should get the
class the symbol names with eval. (We saw a similar use of eval when we
used it to define apply.) Like this:
1 user=> (eval (:__class_symbol__ some-instance))
2 "...pretend there's a class definition here..."
5.4 Exercises
You can find the source for make and send-to, as well as a definition of the
Point class, in sources/class.clj. My solutions are in
solutions/class.clj.
Exercise 1: The last two steps of make and send-to are very similar. Both
look up an instance method in a class, then apply that method to an object
and arguments. Extract a common function apply-message-to that takes a
class, an instance, a message, and a sequence of arguments. Like this:
1 (def apply-message-to
2 (fn [class instance message args]
3 ...))
4
5 (apply-message-to Point a-point :shift [1 3])
It should use the class (a map, not a symbol) and the message (a keyword)
to find a method/function. It should apply that method to the instance and
the args. (You may have noticed that you can get the class from the
instance, so those two separate parameters are not strictly needed. I
thought it worthwhile to give each of the key values names in the parameter
list.)
Exercise 2: Up until now, the :class message has returned the symbol
naming the class. That was OK while an object’s class was nothing but a
symbol. But now it’d be more appropriate to have class-name return the
symbol and class return the actual class map. Implement those methods so
that this code works:
1 user=> (def point (make Point 1 2))
2 user=> (send-to point :class-name)
3 Point
4 user=> (send-to point :class)
5 {:__own_symbol__ Point, ....}}
I think you’ll agree that’s not the best possible result. However, we’re not
going to keep the previous exercise’s automatic accessors going forward, so
it’s not worth fixing.
1. Clojure and other Lisps let you define your own special forms with
macros. It’s one of their greatest strengths. The Lisp message is:
“Don’t like the syntax? Make up your own! (As long as it has lots of
parentheses.)” If I were really embedding this OO language in Clojure,
I’d use macros to let you write something closer to the class definitions
other languages provide. But attractive class definitions is not the point
of this book.↩
6. Inheritance (and Recursion)
Are you a person who likes to follow along in the repl while reading the book? If so,
start by either using your solution from the last chapter or using mine:
When I show longer redefinitions that have only a few lines of changes, I’ll leave parts
out. You can find the full redefinitions in sources/java.clj. That’s the implementation
we’re building in this chapter.
The source is also useful when copying-and-pasting from the book’s PDF isn’t working
because of missing newlines.
Our Point class has its own class and class-name methods. But those
methods should work with any object. So now is a good time to implement
inheritance.
But first…
6.1 Assertions
As we’ve been working along, we’ve been using maps as classes and
symbols to refer to classes. As our class system got more complicated
during the writing of this chapter and the optional ones in Part V, I found
myself sometimes passing symbols to functions that expected maps and
vice-versa.
In some sense, that’s nice: you can write the wrong code—passing a
map to a function that expects a symbol—and have it still work. What
I’ve found, though, is that hampers later change when a function that
has been working for two chapters suddenly fails in a mysterious way.
I’ve been involved in the endless debate between statically type-checked languages (like
Java or Haskell) and dynamically-typed languages (like Clojure or Ruby) for almost 30
years. I started out on the static side but have found myself on the dynamic side,
although in a live-and-let-live, whatever-floats-your-boat kind of way. The
unpleasantness in this chapter hasn’t changed my my mind, since it’s literally the first
time I’ve felt the need to add asserts to my Clojure code.
Here is Anything:
1 (def Anything
2 {
3 :__own_symbol__ 'Anything
4 :__instance_methods__
5 {
6 ;; default constructor
7 :add-instance-values identity
8
9 ;; these two methods have been pulled up from Point.
10 :class-name :__class_symbol__
11 :class (fn [this] (class-from-instance this))
12 }
13 })
Notice that I gave Anything a default constructor. That way, classes that
don’t need any initialization don’t have to define their own :add-instance-
values. Since the default constructor should do nothing by default—merely
return the same map it was given—I defined it with Clojure’s identity
function, which does just that (for any kind of argument).
We can apply methods from the merged map, which contains elements from
both classes:
1 user=> ( (:class-name merged) (make Point 1 2))
2 Point
3 user=> ( (:shift merged) (make Point 1 2) 100 200)
4 {:y 202, :x 101, :__class_symbol__ Point}
We do.
What follows is the process we just worked through manually put into a
named function. Although we’ve been working with class names (symbols)
so far, I’m going to have the function take a class as its argument. That will
save some typing later.
1 (def method-cache
2 (fn [class]
3 (let [class-symbol (:__own_symbol__ class)
4 method-maps (map class-instance-methods
5 (lineage class-symbol))]
6 (apply merge method-maps))))
Real OO systems do a lot of caching of this sort, though I’m sure Java’s is
more sophisticated than what I’ve described.
In our case, we won’t really cache the results of the merge, because
showing you how you’d do that in Clojure doesn’t fit the theme of this
book.
To implement method-cache, we need to implement two functions, class-
instance-methods and lineage. Of the two, class-instance-methods is
the easiest, because it’s a use of eval like we saw in the previous chapter:
1 (def class-instance-methods
2 (fn [class-symbol]
3 (:__instance_methods__ (eval class-symbol))))
lineage is trickier.
6.3 Recursion
To make the explanation of lineage clearer, I’m going to pretend that we
have a subclass of Point called RedPoint. Here’s a picture of our class
hierarchy:
I’m drawing the classes as clouds because there’s a lot of stuff in them
that’s irrelevant to calculating RedPoint’s lineage. I’m drawing crooked
lines from subclass to superclass because we don’t have a direct pointer or
link; instead we have to do the now-familiar eval trickery to move from
one to the other. However, that can all be handled within a function that we
can treat like a direct link:
1 (def class-symbol-above
2 (fn [class-symbol]
3 (:__superclass_symbol__ (eval class-symbol))))
We’d be happy if we could use sequence functions like map to traverse those
links, but we can’t: these classes aren’t contained within sequences. That
means we fall back to the functional programming equivalent of loops:
recursion. A recursive function is one that calls itself. A typical recursive
function does five things:
But there’s a fair amount of artfulness in how you apply those steps to any
given problem.
As we’ve drawn different levels of the diagram, the names along the left
formed this sequence:
1 RedPoint Point Anything nil
How can we turn that into the return value we desire? Well, let’s consider
the last two elements. If you cons something onto nil, you get a one-
element list:
1 user=> (cons 'Anything nil)
2 (Anything)
So if we keep “consing”, we’d get this:
1 user=> (cons 'RedPoint (cons 'Point (cons 'Anything nil)))
2 (RedPoint Point Anything)
That’s the reverse of what we’re looking for. (We want the most specific
class on the right.) The fix is easy:
1 user=> (reverse (cons 'RedPoint (cons 'Point (cons 'Anything nil))))
2 (Anything Point RedPoint)
What we’ve done here is to descend (in the direction of smaller lists),
finding names as we go, and then to ascend back up the lists, collecting
what we’ve found. That’s a typical pattern in recursion:
if heads a special form. It evaluates its first term. Then, depending on the
result, it replaces itself with either the second or third terms. In this case,
RedPoint is a symbol, which is not nil?. So the expansion of the if is the
expansion of the cons list:
1 (cons 'RedPoint
2 (recursive-function (class-symbol-above 'RedPoint)))
The same process happens with Point as with RedPoint, yielding this:
1 (cons 'RedPoint
2 (cons 'Point
3 (recursive-function (class-symbol-above 'Point))))
…and then:
1 (cons 'RedPoint
2 (cons 'Point
3 (cons 'Anything
4 (recursive-function nil))))
… which builds the structure we want. Whew! Notice the shape of the
expanded code echoes the shape of our levels of clouds:
That’s not a coincidence.
Notice that (in this case) the solution is in the right order: we don’t have to
reverse it.
For our particular use of this pattern, the **ending-case?** can again be
nil?, and we again make the smaller structure by using class-symbol-
above. So we can calculate the lineage like this:
1 (def lineage-1
2 (fn [class-symbol so-far]
3 (if (nil? class-symbol)
4 so-far
5 (lineage-1 (class-symbol-above class-symbol)
6 (cons class-symbol so-far)))))
Notice that I named the function lineage-1. That’s because our original
lineage only took one argument, and this pattern uses two. I just have the
real lineage call lineage-1 with an empty sequence:
1 (def lineage
2 (fn [class-symbol]
3 (lineage-1 class-symbol [])))
(As do many languages, Clojure has a way of providing default values for
omitted arguments. It’s a bit awkward for the simple cases, so I’m not using
it in this book.)
Tail recursion
Let’s look at lineage-1 again:
1 (def lineage-1
2 (fn [class-symbol so-far]
3 (if (nil? class-symbol)
4 so-far
5 (lineage-1 (class-symbol-above class-symbol)
6 (cons class-symbol so-far)))))
Suppose we had a class hierarchy with a million classes. That will mean a
million and one calls to lineage-1. At the moment of that last call, the first
call will be waiting on the second, the second will be waiting on the third,
the third… …and the millionth call will be waiting on the the million-and-
first. Every one of those calls will be consuming memory while waiting.
And what will the millionth call do with the result of the million-and-first?
Nothing. The recursive call is the last thing it does, so it just passes the
result back to its caller, which just passes it back to its caller, and so on, all
the way back to the original caller.
This pattern—where the last thing a recursive function does is call itself—is
called tail recursion. Tail recursion is significant because a smart compiler
can abandon the substitution rule and rewrite the function into a loop.
Instead of making a new call to lineage-1, it would just cons the current
class-symbol onto the value of so-far, change the value of class-symbol
to the result of class-symbol-above, jump back to the beginning of the
function, and redo it with the new values. You can’t change symbol-to-value
bindings, but the compiler can.
Not only do loops not risk running out of memory, they run faster.
Many functional languages detect tail recursion and turn it into loops.
Because of limitations of the Java Virtual Machine, Clojure can’t do that
automatically. You have to tell it when you have tail recursion. That’s done
like this:
1 (def lineage-1
2 (fn [class-symbol so-far]
3 (if (nil? class-symbol)
4 so-far
5 ;VVVVV next line
6 (recur (class-symbol-above class-symbol)
7 (cons class-symbol so-far)))))
The second could apply to both patterns, but I found it added complexity in
the exercises for no gain, so we’ll only use it for the first pattern. It lets you
pick an **ending-value** other than nil.
1 (def recursive-function
2 (fn [something so-far]
3 (if (**ending-case?** something)
4 so-far
5 (recursive-function (**smaller-structure-from** something)
6 (**combiner** something so-far)))))
6.4 Exercises
My solutions are in solutions/recursion.clj.
Exercise 1:
Factorial can fit our first recursive pattern, where the sequence of
descending numbers is the structure to make smaller.
Here’s that pattern. Write a factorial that follows it:
1 (def factorial
2 (fn [n]
3 (if (**ending-case?** n)
4 (**ending-value** n)
5 (**combiner** n
6 (recursive-function
7 (**smaller-structure-from** n))))))
Hint: The zero case is an annoying detail. Don’t worry about it at first.
Exercise 2:
Here is the second pattern:
1 (def recursive-function
2 (fn [something so-far]
3 (if (**ending-case?** something)
4 so-far
5 (recursive-function (**smaller-structure-from** something)
6 (**combiner** something so-far)))))
Exercise 3:
Hint: Do you remember the empty? function from the first chapter?
Hint: What function takes a sequence and produces the same sequence
except without the first element?
Exercise 4:
Now change the previous exercise’s function so that it can multiply a list of
numbers. Like this:
1 user=> (recursive-function [1 2 3 4] 1)
2 24
What is the difference between the two functions? Extract that difference,
and make it the first argument to recursive-function.
Exercise 5:
Hint: I don’t think there’s any built-in Clojure function that you can pass in.
You’ll have to write your own with fn.
A bit trickier is producing a map that associates each keyword with its
position in the list:
1 user> (recursive-function **combiner**
2 [:a :b :c]
3 **starting-so-far**)
4 {:a 0, :b 1, :c 2}
Exercise 6:
Pat yourself on the back! You have both used and implemented the built-in
function reduce, perhaps the most dreaded of all sequence functions.
reduce has a different order of arguments, but it does the same thing:
1 user=> (reduce + 0 [1 2 3 4])
2 10
3 user=> (reduce * 1 [1 2 3 4])
4 24
5 user=> (reduce (fn [so-far val] (assoc so-far val 0))
6 {}
7 [:a :b :c])
8 {:c 0, :b 0, :a 0}
9 user=> (reduce (fn [so-far val] (assoc so-far val (count so-far)))
10 {}
11 [:a :b :c])
12 {:c 2, :b 1, :a 0}
Note: when you’re down at the pub and you hear someone mention fold,
know that they’re talking about the same function as reduce, but they’re
either not a Clojure programmer or they are but want to show off to a
Haskell programmer of the appropriate sex.
6.5 Finishing up
We’ve written a method, method-cache, that provides a message-to-method
map for all the messages that an instance can accept, taking inheritance into
account. That’s not yet been hooked into either send-to or make, though.
Let’s do that.
In the exercises for the last chapter, you created a method, apply-message-
to, that applies the method named by a message to an instance and an
argument list. My solution looked like this:
1 (def apply-message-to
2 (fn [class instance message args]
3 (apply (method-from-message message class)
4 instance args)))
Note: message will have a keyword substituted into it, so it is used to look
up a method by key in the method-cache map.
After that, send-to and make will work. Their code doesn’t have to be
changed from the previous chapters.
1 user=> (send-to (make Point 1 2) :class-name)
2 Point
3 user=> (send-to (make Point 1 2) :shift 3 4)
4 {:y 6, :x 4, :__class_symbol__ Point}
That prepares you for the next part of the book, which abandons objects to
concentrate on the different idioms and habits of functional programming.
If, however, you’d like to learn more about object systems, I suggest first
taking a side trip to Part V, where you’ll extend this Java-like model to look
like Ruby’s.
And most any language has basic datatypes. When I begin to talk about
Clojure programs that use maps, you could easily point to Ruby’s Hash
datatype or Java’s Hashmap: both classes that correspond nicely to
Clojure’s maps. So how can the use of something every language has really
characterize “functional programming”?
Exactly in this way: people can do these things in Ruby or Java, and they
often should do them, but they don’t. As a result, there’s a tradition—a lore
—of functional programming, as it is today, that is barely known to object-
oriented programmers. This part of the book aims explain that lore to you
so that, when you’re facing a design problem, you can think “Oh, I could
solve this in a functional style, and that would be sweet.” My recent
contracting work has been in Ruby and Rails, and I find myself doing that a
pleasing amount of the time.
7. Basic Datatypes that Flow through
Functions
In the previous part of the book, we built an object-oriented sub-language.
As is typical with object-oriented languages, ours used a metaphor of
methods (functions) being somehow attached to clumps of data (maps) and
only accessible via that data.
Each object has a relatively small, relatively fixed set of neighbors. Each
execution of the program may produce a somewhat different object graph,
but there’ll be a strong family resemblance between all of them. What’s
different about two executions is mainly about how the objects create and
use their neighbors in response to variations in the input.
I’ll call this dataflow style. Note that, just as the different flows may have
different helper functions, each flow will operate on its own custom “shape”
of data, tailored to its particular purpose.
One way to think about this is that a class will have many client classes, so
its emphasis is on behaviors that will be helpful to any of them. A flow does
not try to be so accommodating. It’s solving a narrow problem, using
functions and data tailored to that problem.
For this chapter, we’re going to deal with the flow provoked when a
registrant asks to see which courses are available to her. The result should
be two sequences, one for the morning courses and one for the afternoon.
(The morning one should come first.)
The sequences should include all the courses our registrant is already signed
up for. They should not include full classes (unless she’s registered for
them.) If all the instructors are allocated, the sequences should not include
courses no one has signed up for.
For each course, the results should contain the course name, the number of
people registered, the number of spaces still available, and whether the
registrant is registered. Specifically, the sequences should contain maps that
look like this:
1 {:course-name "Zigging",
2 :morning? true,
3 :registered 5,
4 :spaces-left 2,
5 :already-in? true}
A warning
My solution will, with abandon, use map, filter, and other sequence
functions. If you think of each of those functions as looping over a
sequence, constructing a new sequence, and passing it on to the next map or
filter, my solution will seem so inefficient as to border on professional
malpractice. Hold off on that judgment until you’ve read the chapter on
pushing bookkeeping into the runtime.
There are two (sequential) parts to the answer. First, we annotate the maps
given to solution with useful information. Second, we use sequence
functions like filter to hide the ifs from our sight (in much the way that
inheritance hides branching on object class from the object-oriented
programmer’s sight).
So, for example, to work with all the full courses, we wouldn’t write code
like this:
1 (if (= (:limit course) (:registered course)) ...
In the annotation phase of solution, we’ll add (via assoc) five fields to the
course map:
Or, rather, it gives us much more than the answer. We’ll produce maps with
a lot of information that the output isn’t supposed to contain. But there’s a
map function (select-keys) that makes it easy to get rid of the unwanted
information.
In later parts of the chapter, I’ll detail the solution. For one small part of the
solution, it’ll use a new datatype. Since that datatype is another generally
useful one, it’s worth explaining in a bit of detail.
7.3 Sets
The set function takes any sequence as an argument and converts it to a set,
removing all duplicates in the process.
1 user=> (set [1 2 1 3 1])
2 #{1 2 3}
You can use the contains? function to find if an element is in the set:
1 user=> (contains? #{1 2 3} 2)
2 true
A set is also a callable. It’s given a single value as an argument. If the value
is in the set, it’s returned. If it’s not, nil is returned:
1 user=> (#{1 2 3} 1)
2 1
3 user=> (#{1 2 3} 4)
4 nil
Such calls are often used instead of contains? (so long as you don’t care
that return values are merely “truthy” and “falsey”, not specifically true
and false).
Many of the usual set operations aren’t pre-loaded in Clojure. You retrieve
them like this:
1 user=> (use 'clojure.set)
You can use filter and other sequence operations on sets, but the result is
a sequence, not a set:
1 user=> (filter odd? #{1 2 3})
2 (1 3)
3 user=> (set? (filter odd? #{1 2 3}))
4 false
For that reason, there’s a select function that acts like filter but returns a
set:
1 user=> (select odd? #{1 2 3})
2 #{1 3}
I’ll separate map annotation into three functions. The first uses the sequence
of course maps and the other sequence of course names to add two fields
that the answer requires:
1 (def answer-annotations
2 (fn [courses registrants-courses]
3 (let [checking-set (set registrants-courses)]
4 (map (fn [course]
5 (assoc course
6 :spaces-left (- (:limit course)
7 (:registered course))
8 :already-in? (contains? checking-set
9 (:course-name
course))))
10 courses))))
There are techniques that avoid fragility, ably described in Freeman and
Pryce’s fine Growing Object-Oriented Software, Guided by Tests, but they
require skill and discipline. This functional style “flattens out” object
relationships, which I find is easier on my brain and saves me time. (How
we get the flattened graphs is the topic of the next chapter.)
The next function adds two useful keys. I’m doing that mainly to avoid ugly
ifs in later code, but—as is so often the case—making code nice is the
same thing as modeling the problem. “Empty” and “full” are adjectives
from the problem domain, and it’s appropriate that we put them in the
solution domain.
1 (def domain-annotations
2 (fn [courses]
3 (map (fn [course]
4 (assoc course
5 :empty? (zero? (:registered course))
6 :full? (zero? (:spaces-left course))))
7 courses)))
Here’s a test:
1 user=> (domain-annotations [{:registered 1, :spaces-left 1},
2 {:registered 0, :spaces-left 1},
3 {:registered 1, :spaces-left 0}])
4 ({:registered 1, :spaces-left 1, :full? false, :empty? false,}
5 {:registered 0, :spaces-left 1, :full? false, :empty? true, }
6 {:registered 1, :spaces-left 0 :full? true, :empty? false, })
Once again, the test can focus on exactly the test data that matters, which is
a considerable win for test clarity.
Another idea from the problem domain is the idea of an unavailable course,
which I will represent with an :unavailable? key. I didn’t include it in
domain-annotations for two reasons:
It’s the only key that depends upon the instructor count, and the
separate function makes that clear.
Its value depends on whether the course map is full, empty, or neither.
Setting it in domain-annotations would mean that domain-
annotations and answer-annotations would look pretty different.
Putting it in its own function means all three functions look roughly
the same.
1 (def note-unavailability
2 (fn [courses instructor-count]
3 (let [out-of-instructors?
4 (= instructor-count
5 (count (filter (fn [course] (not (:empty? course)))
6 courses)))]
7 (map (fn [course]
8 (assoc course
9 :unavailable? (or (:full? course)
10 (and out-of-instructors?
11 (:empty? course)))))
12 courses))))
To put the three functions together, we just have to chain them so that the
sequence returned by one is used as an argument to the next. But here we
run into a problem. Here’s a solution that nests the three functions:
1 (def annotate
2 (fn [courses registrants-courses instructor-count]
3 (note-unavailability (domain-annotations
4 (answer-annotations courses
5 registrants-courses))
6 instructor-count)))
That’s ugly, and it obscures the step-by-step flow. Those of us whose native
languages are written left to right typically think of time as flowing in that
direction and then from top to bottom. In annotate, the flow is from the
middle and up and to the left.
-> translates the code after it into a nested series of function calls, following
these rules:
1. The first element is inserted (as defined by the next two steps) into the
second, making a new element, which is inserted into the third, and so
on.
2. When the next element is a list, the previous element is inserted as its
second element. Thus,
(-> 1 (- 2)) is the same as (- 1 2).
3. When the next element is not a list, it’s converted to a single-element
list, whereupon the previous rule applies. Thus, (-> 1 inc) becomes
(inc 1).
Being able to read this style is important for the rest of this chapter and
book, so take a few moments to practice.
Exercises
My solutions are in solutions/arrows.clj.
Exercise 1:
Use -> to process [1] by removing the number from the vector,
incrementing it, and wrapping it in a list. The sequence of values would be
this:
1 [1]
2 1
3 2
4 (2)
Exercise 2:
Add a step to the previous example. After incrementing the value, multiply
it by 3, for this sequence of values:
1 [1]
2 1
3 2
4 6
5 (6)
Hint: The multiplication will be done by a form similar to the first and third
steps in annotate.
Exercise 3:
Exercise 4:
Once the two kinds of courses are separated, the one on the right should
have :unavailable? courses removed. That looks like this:
You could capture the two halves for later work like this:
1 (let [both (separate odd? [1 2 3 4 5])
2 odds (first both)
3 evens (second both)]
4 ...)
But surely we don’t live in a world so depressing and hostile that it requires
three lines for such a simple operation? Indeed we do not:
1 (let [ [odds evens] (separate odd? [1 2 3 4 5])]
2 ...)
7.8 Finishing up
There’s little of interesting remaining. Given that we’ve annotated the
course maps with much information that’s only of temporary interest, we
need a function that strips that out:
1 (def final-shape
2 (fn [courses]
3 (let [desired-keys [:course-name :morning? :registered :spaces-left
4 :already-in?]]
5 (map (fn [course]
6 (select-keys course desired-keys))
7 courses))))
The two halves of the day should be independent. The availability of the
morning version of a course has nothing to do with its availability in the
afternoon. So it makes sense to process the two halves of the day separately.
That’ll be done in half-day-solution. It performs several steps:
Note that we have to rely on fn when sorting because sort-by doesn’t take
a collection as the first argument.
With all that done, our solution function need only separate its input into
morning and afternoon courses, process each, and return both results:
1 (def solution
2 (fn [courses registrants-courses instructor-count]
3 (map (fn [courses]
4 (half-day-solution courses registrants-courses
5 instructor-count))
6 (separate :morning? courses))))
There you have it: a pretty functional solution to our problem. But that
solution is of no use by itself. It probably lives within a non-functional
world. The next chapter is about how that might be arranged.
7.9 Exercises
You can start from this source: sources/scheduling.clj. My solutions are in
solutions/scheduling.clj.
Exercise 1
Managers are not allowed to take afternoon courses because that would
make them put in too many hours in a day. Implement that restriction.
Exercise 2
Any course can have one or more prerequisite courses. Make it so that a
registrant can’t see a course if she doesn’t have the prerequisites.
1 (def answer-annotations
2 (fn [courses **REGISTRANTS-COURSES**]
3 (let [checking-set (set **REGISTRANTS-COURSES**)]
4 (map (fn [course]
5 (assoc course
6 :spaces-left (- (:limit course)
7 (:registered course))
8 :already-in? (contains? checking-set
9 (:course-name
course))))
10 courses))))
11
12 (def annotate
13 (fn [courses **REGISTRANTS-COURSES** instructor-count]
14 (-> courses
15 (answer-annotations **REGISTRANTS-COURSES**)
16 domain-annotations
17 (note-unavailability instructor-count))))
18
19 (def solution
20 (fn [courses **REGISTRANTS-COURSES** instructor-count]
21 (map (fn [courses]
22 (half-day-solution courses **REGISTRANTS-COURSES** instructor-
coun\
23 t))
24 (separate :morning? courses))))
I can use my editor’s search-and-replace function with the best of them, but
this duplication is a sign of a deeper problem. Unlike courses, which keeps
changing as it flows through the functions, registrants-courses and
instructor-count are constant for the whole flow. Mixing the two kinds of
values within the same parameter lists obscures what this code is really
doing.
We’ll take advantage of something I’ve just started doing in this chapter,
something I hope is so natural that you didn’t even notice it. I’ve started
having functions referring to symbols bound outside themselves:
1 (def note-unavailability
2 (fn [courses instructor-count]
3 (let [out-of-instructors? ...] ;; <<== binding
4 (map (fn [course] ;; <<== function
boundary
5 (assoc course
6 :unavailable? (or (:full? course)
7 (and out-of-instructors? ;;
<<==
8 (:empty? course)))))
9 courses))))
It’s useful to nest helper functions within other functions, but it also has its
downsides. For one, you can’t test the nested functions independently. And
it’s harder to grab one and use it in a different flow. (You have to add back
in the parameters, but then you have to decide: should I use the new
function in place of the original version, even though it’s got parameters
unnecessary in that context, or do I want to live with two versions of the
same function?) I think nested functions are a bit more difficult to read,
both because of the nesting and because the origin of their values isn’t all in
one place (their own parameter list).
As a result, I will often err on the side of having more top-level functions.
To me, deciding how to partition work into top-level functions feels similar
to deciding which classes to have: how do I divide the solution so that its
structure is clear and meaningful?
Because of that, I decided to change the previous solution so that the three
core parts of the flow (annotating, solving, and removing excess keys) were
highlighted by being their own top-level functions:
1 (def solution
2 (fn [courses registrants-courses instructor-count]
3 (let [core-flow
4 (fn [courses]
5 (-> courses
6 (annotate (set registrants-courses) instructor-count)
7 half-day-solution
8 final-shape))]
9 (map core-flow
10 (separate :morning? courses)))))
11
12 (def annotate
13 (fn [courses registrants-courses instructor-count] ...))
14
15 (def half-day-solution
16 (fn [courses] ...))
17
18 (def final-shape
19 (fn [courses] ...))
The remaining helper functions were divided between annotate and half-
day-solution.
When reading it, keep in mind some advice I first heard from Richard P.
Gabriel: don’t read Lisp functions from top to bottom. Instead, look for the
most important code (typically closer to the bottom of the function, often
the most visually dense) and read it first. Refer upwards to definitions when
you need to understand them. In this code, what I consider the core code
just happens to be written with -> expressions.
You’ll see that I went to some effort to make the -> expressions consistently
represent each function’s core flow—the code you should look at first when
trying to understand the function.
I also redid the previous exercises, using the code I just talked about as the
base. You can see these new exercise solutions in solutions/scheduling-
variant.clj.
We’ve seen one solution in this chapter: private functions can be nested
inside public functions. They are completely inaccessible from the outside,
so it’s impossible to build fragile dependencies on them.
That may seem crude, but let’s not forget that systems written with millions
of lines of C run most of the Internet (in the form of the Linux kernel) and
are, as I write, trundling about on the surface of Mars (the Curiosity rover).
File-based modularity plus conventions plus care can get you a long way.
There are clearly separated functional and object parts of the program, with
the object part in control. The object part occasionally flows data through a
functional nugget to solve some problem. To do so, it converts, or
restructures, object graphs into whatever shapes the dataflow accepts, then
applies an interface function (like last chapter’s solution) to the result. The
interface function returns shapes (maps and sequences, most likely) that the
restructuring layer turns into objects.
My experience has been monolingual: it’s all Ruby, just written in two
different styles. To make things more interesting, I’ll give a bilingual
solution, in which Java code calls Clojure code.
The Java code to convert such objects into Clojure values is a bit tedious,
but straightforward. The Clojure code needs maps. Clojure provides several
variants, all of which implement the clojure.lang.IPersistentMap
interface. We’ll use the concrete class clojure.lang.PersistentHashMap.
Similarly, the Clojure sequence interface is defined by the Sequential
interface, and PersistentVector is an implementation. That given,
populating a Clojure map is a small matter of iteration:
1 // Create what the `solution` function will call `courses`:
2 // a sequence of maps.
3 public Sequential restructureCourses(List<Course> courses) {
4 ArrayList<Map> result = new ArrayList<Map>();
5 for (Course course : courses) {
6 HashMap<Keyword,Object> map = new HashMap<Keyword,Object>();
7 map.put(Keyword.intern("course-name"),
8 course.getTemplate().getName());
9 map.put(Keyword.intern("course-limit"),
10 course.getTemplate().getLimit());
11 map.put(Keyword.intern("morning?"),
12 course.isMorning());
13 map.put(Keyword.intern("registered"),
14 course.getRegistrants().size());
15 result.add(map);
16 }
17 return PersistentVector.create(result);
18 }
They’re structurally the same, just rotated 90 degrees. That means that the
approach of the previous section is wasteful. We start with a database like
this:
Our ORM will want to instantiate nine objects of four classes, slurping up a
lot more information than is needed, in more queries than are required.
Most likely, most of the time, the waste is totally insignificant. It’s not the
performance bottleneck in the system, you’ve already got the object-to-
relational mappings defined (or will need them for other reasons), and your
team knows your mapping framework a lot better than they do SQL.
That’s not to deny that large systems have been written entirely in
functional languages. But large scale systems were written in object-
oriented languages, too, and it wasn’t until the publication of books like
Martin Fowler’s Patterns of Enterprise Application Architecture and Eric
Evans’s Domain-Driven Design that I think you could say “There is a thing
that we’re justified in saying is an object-oriented architectural style.”
We’re not at that point for the functional style yet, so far as I can tell.
But that seems awfully repetitive: wasteful typing, prone to error. It would
be better if there were a function that could make incrementer functions.
Like this:
1 user=> (def inc5 (make-incrementer 5))
2 user=> (def inc3 (make-incrementer 3))
3 user=> (def inc8 (make-incrementer 8))
4 user=> (inc8 0)
5 8
That turns out to be remarkably easy to do. Here’s the definition of make-
incrementer:
1 (def make-incrementer
2 (fn [increment]
3 (fn [x] (+ increment x))))
Consider a call like (make-incrementer 3). By the substitution rule, that
call expands by substituting the actual argument 3 for the parameter
increment:
… and that form, once evaluated, is a function that adds 3 to its argument.
This effect—making values hang around after the exit of the function to
which they’re passed—is called closing over a value, or, in languages that
allow variables to change value, “closing over variables”. (No doubt the
term “closing” seemed perfectly apt to someone somewhere.) A function
that can close over external values is called a closure. I mention that
because now you know why Clojure is called “Clojure”. It’s a portmanteau
word combining “closure” and “Java”.
Suppose that Rich Hickey (the author of Clojure) gave us only an even?
predicate. We could write an odd? easily enough:
1 user=> (def odd?
2 (fn [x]
3 (not (even? x))))
4 user=> (odd? 2)
5 false
It’s interesting to think about the connection between not and complement.
not operates on individual boolean values; complement works on whole
functions that produce individual values. The latter seems more exalted:
instead of dealing with merely a huge number of values, it’s dealing with a
huge number of functions, each of which deals with a huge number of
values.
But that raises a question: what’s special about my-complement? It’s applies
one particular modifier function (not) to other functions. There are
probably a zillion functions that need modification, and a zillion modifiers
to modify them with. For example, what if you had a consistent need to
negate the results of arithmetic computations? You could write this
complement-like function:
The latter style could easily be called “parameter-free”, and so of course it’s
called something else: point-free. (No doubt the analogy of formal
parameters to geometrical points seemed perfectly apt to someone
somewhere.)
It needs to process all pairs until it finds one that doesn’t begin with a
keyword. It selects the pairs to process like this:
1 (take-while (comp keyword? first) pairs)
I don’t think that the generated function needs documentation in either
the form of a name saying what it’s for, or a parameter saying what it
works with.
2. Naming only the function. I have a different use of comp that looks
like this:
1 (comp not not predicate)
I use that because I need to make sure the predicate really returns
either true or false (and not, say, nil as a “falsey” value). Because I
thought that purpose might not be clear, I gave the function a name:
1 (let [strict-predicate (comp not not predicate)] ...)
9.4 Exercises
My solutions are in solutions/higher-order-functions.clj.
Exercise 1
In a free country, you could add 2 to each element of a sequence like this:
1 (map (fn [n] (+ 2 n)) [1 2 3])
Exercise 2
juxt turns n functions into a single function that returns a vector whose first
element comes from the first function, the second from the second function,
and so on.
1 user=> ( (juxt empty? reverse count) [:a :b :c])
2 [false (:c :b :a) 3]
Exercise 3
Can you explain, using the substitution rule, why you get those results?
Exercise 4
If let didn’t exist, could you use functions to achieve the same effect as in
the previous exercise? That is, what code could you wrap around (fn [] x)
to produce an x that was bound to no value outside the function and bound
to 3 inside it?
Hint: let is a form that binds names to values and then executes a body. A
function is a form that binds parameter names to values and then executes a
body.
Exercise 5
Suppose you wanted to set an atom’s value to 33, regardless of its current
value. How would you do that? Keep in mind that swap! demands a
function, not (say) an integer.
Exercise 6
Write a function always that takes a value and produces a function that
returns that value no matter what its arguments are. That is:
1 user=> ( (always 8) 1 'a :foo)
2 8
Exercise 7 2
To practice for this book, I wrote three earlier ones. Here are their ISBNs:
0131774115, 0977716614, and 1934356190—except that one of them
contains a typo. In the next two exercises, you’re to find which one.
First, use map to write a function check-sum that performs the following
calculation on the sequence [4 8 9 3 2]:
1 (+ (* 1 4)
2 (* 2 8)
3 (* 3 9)
4 (* 4 3)
5 (* 5 2))
Exercise 8
(Note: if your isbn? claims two of the numbers aren’t ISBNs, you probably
have an off-by-one error.)
Exercise 9
1 (+ (* 1 4)
2 (* 3 8)
3 (* 1 9)
4 (* 3 4)
5 (* 1 2))
Implement check-sum and upc? and check it against these numbers (also in
the sources):
Exercise 10
The same general “shape” of function will work for checking credit cards,
money orders, and so on. Extract the commonality of isbn? and upc? (and
their respective check-sums) into a function number-checker that can be
used to create either of them. Like this:
1 (def isbn? (number-checker ...))
2 (def upc? (number-checker ...))
What’s the advantage of that, besides a little bit less typing? Well, I agree
with the authors of Growing Object-Oriented Software that the point of
object oriented design is getting a good mental picture of the relationships
between objects. Having lots of little utility classes obscures what’s
important behind what’s merely necessary.
1 class Booking
2 def try_booking(people, accept_some_people)
3 ...
4 booked, rejected = accept_some_people.(people) # <<== New
5 booked.each do ... end
6 rejected.each do ... end
7 ...
8 end
9 ...
That’s is somewhat ugly, but the Booking class is now less coupled to the
Reservation class. It can work with any function that accepts people. Even
in loosey-goosey Ruby, the original Booking class could only work with
classes that have a specific method (accept). In statically-typed languages
like Java, you’d be further restricted to collaborators that match a certain
interface or class.
I emphasize early design because at some point, you’ll likely say, “The heck
with it: Booking just does depend on Reservation”. Then you’ll add a
Reservation parameter to the constructor and let Booking become coupled
to a class rather than to a set of behaviors.
Each value must be used by the next step, and no step can use the value of
any step other than its immediate predecessor.
I visualize the difference between the arrow and let like this:
In both cases, though, there’s no decision points in the flow. Arrows go
where they go, and that’s all there is to it. There’s no way to change the
direction of the flow depending on the value, as symbolized by this:
That limitation can be overcome by imagination and the clever use of
functions, though, as the rest of this chapter will show.
I should note that the desire to rid ourselves of explicit branching is not
unique to functional programming. In object-oriented programming,
polymorphic dispatch selects one of N methods according to both the
message name and receiver type. That lets the programmer convert the idea
behind code like this:
1 if (some_property_of(x) {
2 perform_some_action();
3 } else if (some_other_property_of(x)) {
4 perform_some_other_action();
5 } else if (yet_a_third_property_of(x)) {
6 perform_yet_another_action();
7 }
… into a more rigid (and, so, very often more comprehensible) inheritance
structure, where the multi-way if is hidden inside the normally-invisible
dispatch function.
(Note the double parentheses around the use of nil-patch to force -> to put
the value in the right place, as the single argument to a generated function.)
Note: nil-patch is already built into Clojure under the name fnil. fnil
can substitute values for functions that take more than one argument.
fnil is a cute solution, but nil-patching probably isn’t usually what you
want to do with stray nils. More often, you’ll want to simply skip the entire
rest of the computation and return nil. You can do that with an optional
Clojure library called clojure.algo.monads. It lets you write this let-like
expression:
1 (with-monad maybe-m
2 (domonad [step1-value (function-that-might-produce-nil 1)
3 step2-value (* (inc step1-value) 3)]
4 (dec step2-value)))
The domonad follows the same rules as let: the steps are evaluated in
sequence, ending in the evaluation of the body (the dec expression).
However, the (with-monad maybe-m...) wrapped around the domonad
effectively inserts nil-checking code after each step. If any step produces
nil, the whole domonad immediately returns nil.
This is useful in its own right, but is more interesting because of the
possibilities it opens up: if you can insert nil-checking code after steps,
what else could you usefully insert? Quite a lot, it turns out.
While monads are a good example of the power of functions, they need to
be explained carefully if you’re to understand them. We’ll start by looking
at yet another variant of flow style.
Now, the calculation inside the continuation is itself a multi-step one (first
multiplication, then addition), so we can also rewrite it in continuation
passing style:
1 (-> (+ 1 2)
2 ((fn [step1-value]
3 (-> (* step1-value 3) ;; expanded to
4 ((fn [step2-value] ;; continuation-passing
5 (+ step2-value 4))))))) ;; style
Notice that the nesting of the functions lets any of them refer back to the
results of earlier steps, since those results are bound to symbols in the
nested functions’ parameter lists. So, for example, the body of the bottom
function could use both step1-value and step2-value:
1 (-> (+ 1 2)
2 ((fn [step1-value]
3 (-> (* step1-value 3)
4 ((fn [step2-value]
5 (+ step2-value ;; This function's parameter
6 step1-value))))))) ;; An enclosing function's parameter
10.3 Exercises
My solutions are in solutions/continuation-passing.clj.
Exercise 1
Hint: Don’t forget the need for double parentheses when fn expressions are
used within arrow expressions.
Exercise 2
This code computes the same value as the previous code. Rewrite it into
continuation-passing style.
1 (odd? (count (concat '(a b c) '(d e f))))
Is the resulting code the same as your answer to the first exercise (except
for trivialities like choice of parameter names)?
If so, is it necessarily the same, or could you make an argument that you
could (or should) choose different continuations?
Hint: How might you write (concat '(a b c) '(d e f)) in continuation-
passing style?
Exercise 3
… and then reduce it down to a single value in the normal way. (Some
compilers make such expansions in order to simplify their job, but those
details normally don’t concern us.)
In Clojure and other Lisps, expansions can be defined by programmers. domonad, with-
monad, and the monad form you’ll see shortly were all defined by a programmer named
Konrad Hinsen, the author of the clojure.algo.monads library.
Because user-defined expansions, what the Lisps call macros, aren’t a common feature
in functional languages, I won’t explain them in this book. They’re one of the most
powerful features of Lisps, though, and make Lisp the best language for domain-specific
languages (as long as you don’t mind parentheses).
10.5 Extending continuation-passing style
Let’s look at how we can make continuation-passing style more flexible.
Consider this:
1 (-> (+ 1 2)
2 ((fn [step1-value]
3 (-> (* step1-value 3)
4 ((fn [step2-value]
5 (+ step2-value 4)))))))
Excitingly nested though it is, it’s dull in one important way: it always does
the same thing with the value of a step. It passes it to the continuation. Let’s
introduce a function that is given both the value and the continuation and
decides what to do with them. We’ll call it the decider.
Let’s start by making the decision what it already was: to pass the value to
the continuation. Here’s such a definition:
1 (def decider
2 (fn [step-value continuation]
3 (continuation step-value)))
In the above, the arrow flows the result of (+ 1 2) into the first argument
of decider, which has a second argument that’s the continuation. Without
the arrow, that would look like this:
1 (decider (+ 1 2)
2 (fn [step-1-value]
3 ...))
Now we’ll change decider to refuse to apply the continuation if the step
value is nil:
1 (def decider
2 (fn [step-value continuation]
3 (if (nil? step-value)
4 nil
5 (continuation step-value))))
Here’s a computation
1 (+ (function-that-returns-nil) 3)
If nil flows into decider as its first argument, the continuation is never
called and nil is returned.
Recall that the Maybe monad stops the computation if any step produces
nil. That is, it’s a way of producing an expansion that looks like that which
ended the last section. Here’s a sample use:
1 (with-monad maybe-m
2 (domonad [a (some-function)
3 b 2]
4 (+ a b)))
Because that makes behavior easily substitutable. The code above checks
for nil. You’ll soon write an Error monad that stops the computation if any
step produces a particular datum that means “error”. You could swap that
Error monad in for the Maybe monad by changing a single word:
1 (with-monad error-monad ; <<== one word
2 (domonad [a (some-function)
3 b 2]
4 (+ a b)))
And you could swap out that monad for one, called the Identity monad, that
does exactly what a let does:
1 (with-monad identity-m ; <<== one word
2 (domonad [a (some-function)
3 b 2]
4 (+ a b)))
That code would execute all the steps in order, no matter what.
Now that I’ve justified the three pieces, how do they work together?
The lie
The monad (maybe-m, identity-m) is just a name for the decider function.
Like this:
1 (def maybe-m
2 (fn [step-value continuation]
3 (if (nil? step-value)
4 nil
5 (continuation step-value))))
6
7 (def identity-m
8 (fn [step-value continuation]
9 (continuation step-value))
with-monad just turns into a let that binds its first argument to the name
decider. Given this:
1 (with-monad maybe-m
2 ...)
… we get this:
1 (-> (some-function)
2 (decider (fn [a]
3 (-> 2
4 (decider (fn [b]
5 (+ a b)))))))
… we get this:
1 (let [decider (fn [step-value continuation]
2 (if (nil? step-value)
3 nil
4 (continuation step-value)))]
5 (-> (some-function)
6 (decider (fn [a]
7 (-> 2
8 (decider (fn [b]
9 (+ a b))))))))
Make sure you understand why this whole expression returns nil if some-
function returns nil. And make sure you understand why it returns 3 if
some-function returns 1.
Less of a lie
As it happens, some monads need more than just a decider. In the optional
chapter on the Sequence monad, I’ll call that other function the
“monadifier”.
To be more exact: all monads require both a monadifier and decider, but
some can just use identity for their monadifier. (identity just returns its
argument, unchanged).
The truth
I may have mentioned that I sometimes think functional programming
terminology is perhaps slightly less than optimal for novice understanding.
That is the case with monad terminology.
What I’ve been calling “the decider” is conventionally called “bind”. You
can sort of see where that name comes from: the decider calls a function
(the continuation) with a named parameter, thus binding the value of a step
to a symbol.
But that kind of binding is just the way all continuation-passing expansion
works. What’s special about monads is what else the decider does. When I
was learning monads, I kept getting confused until I hit upon the idea of
thinking of the “bind” function as a decider.
Monads can also contain two optional functions, used for things we won’t
cover in this book. Those (or a flag that says they aren’t provided) have to
be included in the map that defines a monad. Because of that, monads are
usually defined with a helper function, monad:
1 (def maybe-m
2 (monad [m-bind (fn [step-value continuation]
3 (if (nil? step-value)
4 nil
5 (continuation step-value)))
6 m-result identity]))
Although the full truth isn’t relevant to this chapter, and hinders, rather than
helps, your understanding of monads (at least if you’re anything like me),
you need to know it. That’s because you’ll shortly define your own monad.
First, though, a couple of Clojure facts will make that exercise more
pleasant.
It turns out that Clojure has a real (not just conventional) notion of
metadata, which is stored in a map attached to a value. That map can be
retrieved with the meta function. Here’s the metadata for a function:
1 user=> (pprint (meta +))
2 {:ns #<Namespace clojure.core>,
3 :name +,
4 :file "clojure/core.clj",
5 :line 809,
6 :arglists ([] [x] [x y] [x y & more]),
7 :added "1.0",
8 :inline-arities #{2},
9 :inline #<core$_PLUS___inliner clojure.core$_PLUS___inliner@ba336d5>,
10 :doc "Returns the sum of nums. (+) returns 0."}
At this point, a and b are equal values with different metadata. That is,
metadata is ignored in equality checks. Indeed, aside from a few functions
like meta and with-meta, everything ignores metadata.
Not all values can have metadata. For example, a Clojure string is a Java
java.lang.String, so there’s no place to put metadata. Numbers also don’t
have metadata.
However, even values that can’t have metadata can be asked for it:
1 user=> (meta "hi")
2 nil
That’s handy because it means you don’t have to check what kind of value
you have before it’s safe to ask for its metadata. Since keywords applied to
nil return nil, it’s also safe to ask for a boolean value in metadata:
One last bit of information: I gave a map the metadata ` {:type :error}
for a reason. Clojure’s type function uses the metadata. If a value has a
:type key in its metadata, its value is type’s result:
In a later chapter, we’ll see how metadata and type can be used to build
something that looks like an inheritance hierarchy but is more flexible.
10.8 Cond
cond is a multi-way if. Here’s a function that uses a cond expression:
1 user=> (def classify
2 (fn [number]
3 (cond (zero? number)
4 "zero"
5
6 (even? number)
7 "even"
8
9 :else
10 "unclassified")))
11
12 )
13 user=> (classify 5)
14 "unclassified"
cond takes any number of pairs of expressions. They are evaluated in order.
Given a pair, cond evaluates its element. If the result counts as true (that is,
is anything but false and nil), the second element is evaluated and its
result is the result of the entire cond.
Notice that the last pair begins with :else, which counts as true (as do all
keywords). That is, it’s not a special token, just a conventional way to mark
the “otherwise” case.
If none of the first expressions counts as true, the value of the cond is nil.
Here’s the simplest possible example:
1 user=> (cond)
2 nil
10.9 Exercises
Often, receiving a nil from the Maybe monad is not so useful. Something
bad happened somewhere, but you don’t know where or what. Furthermore,
not every error is associated with a nil. In this exercise, you’ll implement a
monad that has the short-circuiting behavior of the Maybe monad, but
works with error values instead of nils.
Because I know you’re too dignified to write such functions, I’ll predefine
oops! and oopsie? for you.
You can start from sources/maybe.clj, which contains oops! and oopsie?
as well as this definition of the Maybe monad (which I’m calling maybe-
monad so as not to conflict with the predefined maybe-m):
1 (use 'clojure.algo.monads)
2
3 (def decider
4 (fn [step-value continuation]
5 (if (nil? step-value)
6 nil
7 (continuation step-value))))
8
9 (def maybe-monad
10 (monad [m-result identity
11 m-bind decider]))
You’ll have to be sure to use that first line (the use statement) in your repl.
My solution is in solutions/error.clj.
1 user=> (+? 1 2)
2 3
3 user=> (+? nil 2)
4 nil
5 user=> (+? 1 nil)
6 nil
1 user=> (def +?
2 (with-monad maybe-m
3 (m-lift 2 +)))
The only annoying thing about this is that you have to specify the number
of arguments in advance. (That’s because Clojure gives you no way to ask a
function how many arguments it has.)
… and you want errors to short-circuit the calculations, you can do this:
1 (with-monad error-monad
2 (let [defenestrations-of (m-lift 2 defenestrations-of)
3 building (m-lift 1 building)
4 story (m-lift 2 story)]
5 (defenestrations-of (counselors)
6 (-> (building 8) (story 3)))))
That hardly seems worth it for one calculation, but if you often want lifted
behavior from functions, it’s nice that you can add it as easily as this:
1 (with-monad error-monad
2 (def story
3 (m-lift 2
4 (fn [building n] ...)))
5 (def defenestrations-of (m-lift 2 ...))
6 ...)
This is not straight-line flow. In Clojure, you could do a similar thing via
nested map functions, but I claim there’s no solution that isn’t really ugly.
(Try it! Prove me wrong!) For that reason, Clojure provides a special form
that looks like the straight-line flow of let:
1 user=> (for [a [1, 2]
2 b [10, 100]
3 c [-1, 1]]
4 (* a b c))
5 (-10 10 -100 100 -20 20 -200 200)
The idea of looping has been abstracted away. (Indeed, I find that I’m so
stuck in thinking in terms of loops that it hampers my understanding of for
expressions in real code. I read from the top down, trying to understand
loop values, instead of concentrating on the body of the for, which is where
the action is.)
It’s important to realize that a property for shares with let is that later
steps can refer to the results of earlier steps. To see that, here’s a simple
example:
1 user=> (for [a [1 2 3]
2 b (repeat a "hi!")]
3 [a b])
4 ([1 "hi!"]
5 [2 "hi!"] [2 "hi!"]
6 [3 "hi!"] [3 "hi!"] [3 "hi!"])
for is a fine and convenient special form (with some features I haven’t
mentioned), but its status as a special form limits it. Let’s compare it to the
actual Sequence monad, which is used like this:
1 user=> (with-monad sequence-m
2 (domonad [a [1 2 3]
3 b (repeat a "hi!")]
4 [a b]))
5 ([1 "hi!"]
6 [2 "hi!"] [2 "hi!"]
7 [3 "hi!"] [3 "hi!"] [3 "hi!"])
And suppose that I want to combine the behavior of the Maybe monad and
for… well, I can’t. I can with the Sequence monad, though, because all
well-behaved monads can be combined:
1 user=> (def combined-monad (maybe-t sequence-m))
2 user=> (with-monad combined-monad
3 (domonad [a [1 nil 3]
4 b [-1 -2]]
5 (* a b)))
6 (-1 -2 nil -3 -6)
10.12 Exercises
My solution is in solutions/primes.clj.
Exercise 1
2 is a prime number. All multiples of 2 are not primes (except for 1*2).
Here’s how you can use range to find all the non-prime multiples of 2
between 4 (the first one) and 100 (inclusive).
1 user=> (range (* 2 2) 101 2)
2 (4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
3 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100)
We can do the same for 4. That’s wasted work, since any multiple of 4 is
already a multiple of 2, but what the heck: your CPU spends almost all of
its time waiting for you to give it something to do.
1 user=> (range (* 4 2) 101 4)
2 (8 12 16 20 24 28 32 36 40 44 48 52 56 60 64 68 72 76 80 84 88 92 96 100)
Exercise 2 2
Use the Sequence monad or for (your choice!) to find all non-primes less
than 100. Duplicates are OK.
Hint: You’ll need two steps and a body that just returns a value.
Exercise 3
Hint: Here’s how you can tell if 6 is a non-prime less than 10:
1 user=> (#{4 6 8 9} 6)
2 6
“Dizzying”, though, is an operative word. How much can you use monads
before you’ve made a path that the programmers who come after you can’t
follow? Remember: in most cases, you’re not creating abstractions to give
yourself more power; you’re creating them so the people extending your
code have power they can readily exploit.
At the current state of the practice, I believe using the Maybe, Error, and
Sequence monads is perfectly reasonable. Lifting functions with those
monads seems reasonable. Defining new monads, or transforming existing
monads to make new ones, will likely leave those who follow after you
gasping for air.
So we have another branch point in the book. If you find the promise of
monads fascinating and want to understand them better, go to Part III. If
you’re content knowing what you know now and want to move on, go to
the next chapter.
11.1 Trees
In object-oriented languages, it’s not uncommon to have specialized classes
for trees, with nodes having direct pointers to other nodes. The class is
designed according to the expected pattern of traversal:
The zipper data structure, explained next, works with this kind of tree. To
make things interesting, we’ll work with trees whose contents are code:
That code is simultaneously two things: a recursive function that sums up
all the numbers in a tree, and a tree itself. It is a three-element sequence.
The first two are symbols (def and sum-tree). The third element is a nested
subsequence (the fn form). This property—that the primary way to
represent a program is as a basic datatype—rejoices in the name
homoiconicity.
11.2 Zippers
For the moment, let’s think of zippers as a data structure that overlays
pointers on the basic nested sequence shape:
At any given moment, the zipper points to one of the nodes in the tree.
From that node, it can move left, right, up, or down (where down always
moves to the leftmost node of a sequence).
At the beginning, the zipper’s location is at the root of the tree. At any
moment, we can print the subtree at the current location:
1 user=> (zip/node zipper)
2 (1 (2 3) 1)
That’s not so exciting, so let’s descend the tree and see what’s there:
1 user=> (-> zipper zip/down zip/node)
2 1
Zippers have a zip/next function that moves them through the tree left-to-
right and top-to-bottom in a depth-first way.:
During the traversal, the zipper can know if it’s at an interior or leaf node
with zip/branch?. When the zipper produced by zip/next has “fallen off
the end of the tree”, zip/end? is true.
So let’s implement a recursive function that collects all the nodes in a tree,
effectively flattening it. Here’s an example:
1 user=> (flattenize '(fn [a] (* 2 (inc a))))
2 (fn [a] * 2 inc a)
As a start toward that form, let’s think about a function that takes a zipper
and produces a flattened list of nodes:
1 user=> (def flatten-zipper
2 (fn [so-far zipper]
3 (cond (zip/end? zipper)
4 so-far
5
6 (zip/branch? zipper)
7 (flatten-zipper so-far (zip/next zipper))
8
9 :else
10 (flatten-zipper (cons (zip/node zipper) so-far)
11 (zip/next zipper)))))
1. It prints its output backwards because we cons new entries onto the
front of so-far. That’s easy to fix, especially since we won’t be calling
flatten-zipper directly:
1 user=> (def flattenize
2 (fn [tree]
3 (reverse (flatten-zipper '() (zip/seq-zip tree)))))
2. This zipper doesn’t descend into literal vectors or maps; it treats them
as leaves, not branches. That’s the most useful behavior when you’re
working with code. (There are other zippers that behave differently.)
That’s enough information for a first set of exercises with zippers. First, let
me introduce two more Clojure features that might come in handy.
But the “then” and “else” parts of an if and the clauses of a cond accept
only a single form. That means you can’t insert a debugging println in
this:
1 (if (safe-to-launch? a 17)
2 (ignite a))
… to produce this:
1 (if (safe-to-launch? a 17)
2 (println "Success case. Will execute `ignite`.")
3 (ignite a))
… because the ignite form has now become the “else” case, which is
probably bad.
Any symbols on the right-hand side of a let step refer to previous bindings,
not the binding that’s about to be made. Remember that let is equivalent to
the Identity monad, and therefore to this expansion:
1 (-> (inc something)
2 (fn [something] something))
Still, recursive local functions are awfully useful, and it would be a shame
to insist that they can only be used when made globally available with def.
Therefore, there’s a form specifically for defining potentially recursive local
functions: letfn. Here it is:
1 user=> (letfn [(factorial [n]
2 (if (or (= n 0) (= n 1))
3 1
4 (* n (factorial (dec n)))))]
5 (factorial 5))
6 120
The syntax is new. The functions are defined within a vector, as with let.
Each definition has this form:
1 (function-name [args] body...)
That looks like a pointless variation from our usual fn style, but I can
actually now reveal that what we’ve been using is the real oddball. Most
Clojure programs use the following form to define top-level functions:
1 (defn factorial [n]
2 (if (or (= n 0) (= n 1))
3 1
4 (* n (factorial (dec n)))))
That’s shorthand for the (def factorial (fn ...)) form you’ve seen
throughout the book. I’ve used the latter form to emphasize that functions
are values like any others. I’ll continue to use it, just for kicks.
If you want to use recursive helper functions in the exercises, don’t forget
letfn.
11.5 Exercises
You can find the source for flattenize in sources/zipper.clj. My
exercise solutions are in solutions/zipper.clj.
Exercise 1
Exercise 2
Write a function that returns only the first vector in a tree. It should return
nil if there is no vector.
Replacing
So consider the use of zippers to replace all + symbols in a form with PLUS.
We know how to identify when a node is a +:
1 (= (zip/node zipper) '+)
The zip/replace function replaces the current node (which may be a leaf
or a branch) with a new one. Note that replace, like everything else, is an
immutable operation: the original zipper is left unchanged, and a new one is
created. Here’s an example of its use:
1 user=> (def z1 (zip/seq-zip '(+ 1 2)))
2 user=> (def z2 (-> z1 zip/down (zip/replace 'PLUS)))
The new zipper has moved down to the + node and replaced it.
zip/replace leaves the zipper pointing at the same node, so if we view it,
we can see the change:
1 user=> (zip/node z2)
2 PLUS
There are two ways we can see the change to the whole tree. First, we could
move up and use zip/node:
1 user=> (-> z2 zip/up zip/node)
2 (PLUS 1 2)
I find it somewhat easy to get confused in cases like this. That’s because
zippers make it look like you’re editing a single tree, but you actually have
to remember that every zipper operation gives you a new zipper. Consider
this case:
In that situation, I find it easy to think “I should go up, then use
zip/append-child. With that done, I’ll make recursive call to helper on
the zip/next node.” Which leads to this clause in the cond:
1 (cond ...
2 (= (zip/node zipper) '-)
3 (-> zipper
4 zip/up
5 (zip/append-child 55555)
6 zip/next
7 helper)
Do you see the problem? It’s that the zip/next is applied to the results of
zip/append-child, which is the branch node. So zip/next produces a
zipper pointing at the - node:
That will not end well.
It’s better to do the append-child when the recursion first arrives at the
branch with a - child:
1 (cond ...
2 (and (zip/branch? zipper)
3 (= (-> zipper zip/down zip/node) '-))
4 (-> zipper
5 (zip/append-child 55555)
6 zip/next
7 helper)
To make sure it’s clear what’s going on, let’s work with a simple list:
1 user=> (def zipper (zip/seq-zip '(1 2 3)))
It’s important to realize the consequences of that. The previous node is not
(not! not!) necessarily the node to the left. (The emphasis is because I make
this mistake all the time.)
Consider this:
1 user=> (def zipper (zip/seq-zip '((+ 1 HERE) (+ 2 3))))
So, given a zipper pointing at a / node, here is how not to delete both
arguments:
1 user=> (def start (-> (zip/seq-zip '(/ (+ 1 HERE) 2)) zip/down))
2 user=> (def end (-> start zip/right zip/right zip/remove zip/remove))
3 user=> (zip/node end)
4 1
5 user=> (zip/root end)
6 (/ (+ 1))
It has first removed the 2, then the HERE. That’s not the two-argument
deletion we wanted.
Putting together what we know about removing and inserting, we have the
following:
1 (cond ...
2 (= (zip/node zipper) '/)
; (1)
3 (-> zipper
4 zip/right
5 zip/remove
; (2)
6 zip/right
7 zip/remove
8 (zip/insert-right (-> zipper zip/right zip/node)) ; (3)
9 (zip/insert-right (-> zipper zip/right zip/right zip/node)) ; (4)
10 zip/next
; (5)
11 helper)
Here are the steps in the processing. When the zipper points to (* 2 1),
tumult sees that the first child is *. Therefore, it replaces the whole subtree
with this:
1 (/ 1 (+ 3 (- 0 9999))) ;; instead of (* 2 1)
It moves on. When it encounters the (- 0 9999) subtree, it adds 5555 as the
last child:
1 (/ (PLUS 3 (- 0 9999 5555)) 1)
And that’s how a complicated substitution is further processed.
Edit-and-replace
There’s one other function worth explaining here: edit. In its simple form,
it passes the subtree at the current position to a given function, then
zip/replaces the old value with the new:
edit can also take arguments that are passed to the function (after the value
from the tree):
1 user=> (-> zipper (zip/edit - 33) zip/root)
2 (+ -32 2)
“Moving”
“Editing”
Viewing
Predicates
branch?: At a branch?
end?: True when the recursion has moved off the end of the tree. (Not
true of the last element.)
11.8 Exercises
These exercises are a continuation of the last set, so the sources and
solutions are in the same files: sources/zipper.clj and
solutions/zipper.clj.
Exercise 3
If you’re like me, you understand code better after editing it. There’s a lot
of duplication in tumult. Factor it out into helper functions.
Exercise 4
My Clojure unit testing tool, Midje, uses the metaphor the that programmer
first states facts about the program, checks that the supposed facts are
actually lies, then writes the code to make them true. Here are two examples
of facts that are already true:
1 (facts
2 (+ 1 2) => 3
3 3 => odd?)
Internally, Midje works by expanding the arrow forms into something a bit
more like Expectations’ format. The expanded form of the previous
example would look like this:
1 (do
2 (expect (+ 1 2) => 3)
3 (expect 3 => old?))
(The arrows are not syntactic sugar; there are different variants for different
purposes.)
Note that the arrow forms can appear deeply nested in a fact. For example,
they’re often inside let expressions. Here’s an example from Midje’s own
test suite:
1 (fact "Metaconstants print as their name"
2 (let [mc (Metaconstant. '...name... {})]
3 (str mc) => "...name..."
4 (pr-str mc) => "...name..."))
The only exception is that arrows may not be nested inside of either left-
hand or right-hand size of an arrow. That is, the following is illegal:
1 (fact
2 (3 => odd?) => "what could possibly made sense here?")
Exercise 6
Hint: zip/right is problematic because it can move you off the edge of a
subtree (by returning nil if you try to go right when you’re already on the
rightmost node).
Exercise 7
There is an exception to the rule that Midje arrow expressions can’t appear
within other arrow expressions. They can if they’re quoted:
1 (fact
2 (first '((+ 1 2) => 3))
3 => '(+ 1 2))
Hint: This might be trickier than it seems because the final quoted form is
the final subtree in the tree.
Exercise 8
Change transform so that this works. For simplicity’s sake, assume that a
provided clause only contains one arrow form.
Were I working in a language with mutable data structures, I’d only use
zippers when I had a need to look at past states of the tree. But it’s
comforting to know that I can do something moderately similar to what I’m
used to, and in a purely immutable way.
move
change
move
change
move
change
use zip/root to magically make the edited tree appear
That zipper can be passed all over the place. Multiple bits of code could
take it, add different operations to the “program”, and create a custom tree
with zip/root.
Futures are my favorite parallelism construct. To show how they work, let
me introduce Takeuchi’s function, which has the distinction of being very
recursive and very slow:
1 (defn tak [x y z]
2 (if (< y x)
3 (tak (tak (dec x) y z)
4 (tak (dec y) z x)
5 (tak (dec z) x y))
6 z))
(tak 20 3 23) takes two minutes and six seconds on my laptop. However,
this takes no time at all:
1 (def t (future (tak 20 3 23)))
I can ask for the value of the future computation immediately, like this:
1 user=> (deref t)
In that case, the caller must wait for the computation to finish. But I
wouldn’t use a future if I wanted to so tightly couple the initiation the
computation to the use of its results. Instead, I would write code that went
off and did other things. Only when it absolutely needed the results would it
ask for them. If the computation had finished, they’d be immediately
available.
Exercise 1
The first version of our zipper will be able to do nothing but go down and
produce subtrees. It will begin with this no-op data structure, where a zipper
is just the original sequence.
1 (def seq-zip
2 (fn [tree] tree))
Given that, implement zdown and znode such that the following is true:
1 user=> (-> '(a b c) seq-zip zdown znode)
2 a
3 user=> (-> '( (+ 1 2) 3 4) seq-zip zdown znode)
4 (+ 1 2)
5 user=> (-> '( (+ 1 2) 3 4) seq-zip zdown zdown znode)
6 +
Exercise 2
Exercise 3
Now let’s add lateral movement. That implies that the zipper data structure
must keep track of what’s to the left and right of the current node:
1 (def seq-zip
2 (fn [tree]
3 {:here tree
4 :parents '()
5 :lefts '()
6 :rights '()}))
Implement zright, and zleft. Change any other functions to make the
following work:
1 user=> (-> (seq-zip '(a b c)) zdown zright znode)
2 b
3 user=> (-> (seq-zip '(a b c)) zdown zright zright zleft znode)
4 b
5 user=> (-> (seq-zip '(a b c)) zdown zleft)
6 nil
7 user=> (-> (seq-zip '(a b c)) zdown zright zright zright)
8 nil
9 user=> (-> (seq-zip '(a b c)) zdown zup znode)
10 (a b c)
Exercise 4
Exercise 5
Here’s the clever part. Whenever a zreplace is done on a zipper, that zipper
is marked as having been changed:
1 (def zreplace
2 (fn [zipper subtree]
3 (assoc zipper
4 :here subtree
5 :changed true))) ;; <<==
When you move up from a changed node, two things have to happen:
1. The zipper for the destination branch must get a :here node that
incorporates the :here of the changed node.
2. That new zipper itself must be marked as :changed so that any
movement up will propagate the change. In particular, zroot must
implement all the changes all the way to the top of the tree.
Note that the new behavior for :changed does not affect the fact that zup
returns nil if it tries to go too high:
1 user=> (-> (seq-zip '(a)) zdown (zreplace 3) zup zup)
2 nil
Exercise 6
Implement znext. (I suggest doing it in several steps, one group of
examples at a time.)
… except that if the branch is empty, there’s no first child for znext to
move to, so it moves right:
1 user=> (-> (seq-zip '(() b)) znext znext znode)
2 b
3. If znext needs to move further right and cannot, it should move up and
try moving right again.
1 user=> (-> (seq-zip '((a) b)) zdown zdown znext znode)
2 b
Note that the attempt to move right could again fail, in which case
znext should try moving up again:
1 user=> (-> (seq-zip '(((a)) b)) zdown zdown zdown znext znode)
2 b
We won’t worry yet about what happens if znext “falls off the top”.
C does not pretend your vector is anything like an object. It’s perfectly
happy for you to add the integer 1 to it, giving you an index into its interior.
You can think of that as a subvector if you like, but that’s only your opinion
about what a bunch of integers means: there’s nothing in the runtime layout
of memory to imply that. And if you add another 1, moving you past the
“end” of your “two-element” “vector”… Well, best of luck to you if your
code decides to interpret that as the index of another string.
C also lets you write new data on top of old. If you do that, it is entirely
your responsibility to ensure that there’s no code that assumes the locations
still contain the old data.
Back in 1981, the idea that garbage collection could be used in real
applications was entertained only by lunatics. It took some fifteen years
before Java made garbage collection respectable.
But Java still permits you to make assumptions about your data. You know
that a vector of strings looks something like this:
You don’t know where things are in memory, and you can’t just point into
the middle of a vector and call that a new vector, but a Java programmer
still knows that if she makes a sorted copy of a vector, the result will be
something like this:
She knows other things, too:
… that there’s no overlap between the old and new vectors. She can
change either independently of the other.
… that the first element of the original vector is the identically same
object as the second element of the sorted vector.
… that the copy-and-sort function won’t return until it completes
creating the new objects.
Functional languages give you even less knowledge of your data. Even
more than object-oriented languages do, they hide from you the awkward
truth that the story C tells is the true one1.
In this chapter, I’ll reveal more of the truth behind functional data
structures.
12.1 Laziness
I’ve been promising for many chapters to explain why code like this:
1 user=> (first (range 0 1000))
… isn’t crazy. Finally, I deliver on that promise. Before that, let me you
remind you why it seems crazy.
We know how Clojure evaluation works. Given the expression above, the
first step is to evaluate first to yield a function:
1 user=> first
2 #<core$first clojure.core$first@528a52b6>
And finally, the function would be applied to yield 0, with all 999
remaining values ignored.
Those evaluation rules are correct, but range doesn’t return just any
sequence. It returns a lazy sequence; that is, an object of a very particular
Java type:
1 user=> (type (range 1000))
2 clojure.lang.LazySeq
Values of type LazySeq are usually called lazyseqs. I’ll use that terminology
except when I’m talking specifically about the Java class that implements
them.
A lazyseq only calculates its first value when some function like first asks
for it. Therefore, in the previous example, the value of range is a lazyseq
that stashes the start value 0 away and returns it when first asks for it. The
value 1 is never calculated unless some code asks for the first of the rest
of the lazyseq2. All 1000 numbers would only be only calculated for code
that explicitly asks for each element. count would be an example of such a
function.
range is far from the only function that produces a lazyseq. Here are others
you’ve been using throughout the book:
map
filter
take
concat
distinct
keep
interleave
drop
partition
repeat
Given how common those functions are, most any flow you start with a
vector or a list ends up working with lazyseqs pretty quickly. Consider a
function like this:
1 user=> (def some-numbers
2 (filter (partial < 200)
3 (map factorial [5 8 9 300 1000])))
map initially does nothing with its arguments except produce a lazyseq.
filter does the same: it produces a lazyseq that just points to the lazyseq it
got from map. So no real computation happens here, just the creation of
some data, which I’ll represent like this:
Now, let’s execute this code:
1 user=> (first some-numbers)
first asks the filter lazyseq for its first value. In order to calculate that,
filter must ask the map lazyseq for its first value. In order to calculate that,
map needs the first value of its sequence argument. Here’s the picture so far:
Because map’s sequence argument is a vector, the first value is readily
available. map applies factorial to get 120, which it gives to filter:
120 doesn’t pass filter’s predicate (since (< 200 120) is false), so
filter asks for the map’s second element, which causes another flow of
function calls down the hierarchy and values up it:
Since 40320 passes the filter, filter returns it to first. And the calculation
is complete.
It’s important to note that this sequence of calculations has collapsed what
appears to be two loops (one for mapping factorial and one for filtering)
into what amounts to a single loop with an early exit:
1 for (int number in [5, 8, 9, 300, 1000]) {
2 int result = factorial(number);
3 if (number > 200) return number;
4 }
It would be unwise to try to print either of those sequences (since the printer
has to produce each of the infinite number of integers). But as long as you
avoid functions like printing, such infinite lists can come in handy.
In the chapter on higher-order functions, you wrote code that checked the
validity of ISBNs and other “self-checking numbers”. Here was one of
those functions:
1 (def check-sum
2 (fn [sequence]
3 (apply + (map *
4 (range 1 (inc (count sequence))) ; <<== look here
5 sequence))))
Notice that it goes to some trouble to make the second argument to map the
correctly-sized sequence of integers. But that’s unnecessary. Since map only
processes until it runs out of elements in any of its sequence arguments, we
can use an infinite lazyseq to simplify the code:
1 (def check-sum
2 (fn [sequence]
3 (apply + (map *
4 one-and-so-forth ; <<== here
5 sequence))))
This exemplifies something that laziness gives you: you have the freedom
to (appear to) generate way too much data, confident that only the amount
actually needed will be created. That can eliminate tedious and error-prone
bookkeeping.
That function returns a Cons whose first element is the value passed in as
first:
1 (def rrange
2 (fn [first past-end]
3 (new clojure.lang.LazySeq
4 (fn []
5 (cons first ...))))) ; <<==
What is the rest of that cons? It’s a lazyseq whose function calculates (inc
first). That is, it’s a recursive rrange:
1 (def rrange
2 (fn [first past-end]
3 (new clojure.lang.LazySeq
4 (fn []
5 (cons first
6 (rrange (inc first) past-end)))))) ; <<==
Oops. I forgot to use the past-end argument. A LazySeq indicates that the
sequence is complete by returning nil:
1 (def rrange
2 (fn [first past-end]
3 (new clojure.lang.LazySeq
4 (fn []
5 (if (= first past-end) ; <<==
6 nil ; <<==
7 (cons first
8 (rrange (inc first) past-end)))))))
12.4 Exercises
You can use rrange as a model for these exercises. It’s in
sources/lazy.clj. My solutions are in solutions/lazy.clj.
Exercise 1
Exercise 2
Exercise 3
Since it’s easy for sequence functions to “inherit” laziness from functions
they use, sources/lazy.clj contains an eager? function you can use to
check your work:
1 user=> (eager? filter)
2 false
3 user=> (eager? ffilter) ; my solution to the previous exercise
4 false
5 user=> (eager? eager-filter)
6 true
As you’ll see, there’s a close family resemblance between a recursive
version of a function and a lazy version. Once you know how to write the
recursive version, you can easily write the lazy one.
If you’re curious, the .readLine notation is how you call a Java method in Clojure. In
general, if you wrote this in Java:
1 object.message(arg1, arg2);
It would be unwise to try to print that sequence, since it never ends, but we
can print just the first four values:
1 user=> (take 4 inputs-starting-now)
2 (> 1
3 > 2
4 "1" > 3
5 "2" > 4
6 "3" "4")
eval executes the take expression. take returns a lazyseq that print is to
print. At that point, nothing has been done to the lazyseq but setup. That is,
print has just been given this:
What does print do? The first thing is to check the type of its argument to
see how to print it. A lazyseq is printed just like a list, so print prints a
parenthesis before even fetching the first value. Which looks like this:
1 user=> (take 4 inputs-starting-now)
2 (
Since it’s printing a sequence, print now asks for its first element. Only
now is the lazyseq triggered to evaluate its function. That means printing
the prompt and reading what’s typed. The state of your screen now looks
like this:
1 user=> (take 4 inputs-starting-now)
2 (> 1
And before the printer prints the second element, it checks if the third
exists:
1 user=> (take 4 inputs-starting-now)
2 (> 1
3 > 2
4 "1" > 3
5 "2"
And so on. When the sequence is complete (when the final LazySeq’s
function produces nil), the remaining elements are printed.
The precise details are of no great interest. The more important point is that
debugging-via-print-statements can get confusing in languages with lazy
evaluation. In Clojure, one way to avoid that confusion is through the use of
the doall function. If you give it a lazyseq, it forces all elements to be
calculated before it returns. Here’s how it’s used:
1 user=> (doall (take 4 inputs-starting-now))
2 ("1" "2" "3" "4")
Did you find that result surprising? Did you expect prompts? That’s an easy
mistake to make. Remember that a LazySeq only applies its element-
producing function once. After that, the cached value is returned. We
already gave the four values to four prompts, so (take 4) causes no new
evaluations.
To cause a new prompt, we’d have to ask for the next unused element:
1 user=> (take 5 inputs-starting-now)
2 ("1" "2" "3" > 5
3 "4" "5")
Notice how all this “first this happens, then that happens, and if it happens
again it doesn’t really happen” business seems clunkier than the use of
lazyseqs earlier in the book (where I hope you never even noticed a
difference between a lazyseq and a plain old list)? Why this sudden
awkwardness? This is our first use of mutable state and functions with side
effects5 (specifically, I/O). A functional enthusiast would claim that such
awkwardness is inevitable when you have mutable state. We’re just noticing
it because we’ve been free of it throughout the book. We’re like a friend of
mine who had a job that made him fly across the Atlantic every week. He
claimed he never got jet-lagged until he stopped flying and realized that, no,
he’d always been jet-lagged.
I think that overstates the case, but I have to admit that mutable state seems
odder and odder the more I use Clojure.
But let’s leave clunkiness aside and observe what we’ve done:
1 (def inputs-starting-now (repeatedly prompt-and-read))
I think that’s just the bee’s knees… neat-o, daddy-o… cool… groovy…
legit. I hope you do too.
12.6 Exercises
Starting code for these exercises is in sources/lazy-world.clj. Except for
the first exercise, my solutions are in solutions/lazy-world.clj.
Exercise 1
What do you expect the result of the following to be? What do you actually
see? Can you explain it?
1 user=> (first singles)
My solution to this exercise has pictures, so you can see it at the end of this
section.
Exercise 2
number-string?: returns true if Java can parse the string into an integer.
Before the first input is read, the structure of the sequences looks like this:
The arrow pointing down from singles function to the inputs lazyseq is
because that function must have a reference to inputs in order to ask it (via
first) for its first element.
In order to produce its first value, the singles function must ask for the
first element of the inputs lazyseq. That produces a structure like the
following, which I explain in some detail below:
In response to a request from singles, the inputs lazyseq executed its
function, producing a Cons that points to a cached value of the first string
read—and also to a function that, when called, will produce the next Cons
in the sequence. Because the whole inputs sequence was created with
repeatedly, the same function can always be reused to create the next
element.
When the first value is returned to the singles LazySeq, that object also
caches the value in a Cons. That Cons also points to a function that can
produce a next sequence value. In this case, though, that function cannot be
the same as the one that created it. It must point to the inputs lazyseq that
produces its second value.
The problem in this exercise was: what happens when you rest inputs with
the following?
1 (def inputs (repeatedly prompt-and-read))
The effect of that form is to bind a new lazyseq to inputs. The new lazyseq
is completely independent of the old. That situation looks like this:
You can see that retrieving the first value of singles will still return "1".
What inputs is bound to has no effect. (Such are the perils of code with
state.)
One more small note: Since nothing points to the original inputs lazyseq, it
will be garbage-collected away:
12.7 Immutability through structure sharing
I have to admit it: I worry about wasting memory. My first post-college
programming job was on a computer that had 65,536 16-bit words, and it
seems that warped me for life. A non-obsessive person would just say this
about immutability:
In fact, I think I will say that. The algorithms are complicated enough that I
don’t care to learn them, and it would take too much time to explain them,
so I’ll force myself to operate on faith—and I think you should too. If
you’re not, there’s a vast literature on efficient data structures for functional
languages. For example, Clojure’s PersistentHashMap data structure uses
Phil Bagwell’s Hash Array Mapped Trie, which you can find out about here
and here. The textbook on the topic is Functional Data Structures by Chris
Okasaki.
Have at it!
I think the most important thing is that immutability and laziness are at least
desirable: if you don’t have to give up much to get them, why not?
1. In Ruby, you could use the build-in Hash class but simply always use
merge (to create a copy with a difference) instead of []= (to modify a
hash in place). In Java, you might work with your own subclass of
HashMap that implements a copy-and-putAll version of merge. When
the profiler tells you you have a bottleneck, optimize by using well-
encapsulated mutation.
2. If you organize much of your program as a series of flow functions
that mutate the structures that pass through them, I believe you’ll be
almost as safe as if the functions made copies. Since such functions
only depends on their inputs, they won’t break because of some
unexpected change to shared state.
It’s more awkward, I think, to simulate laziness. You can pass functions
around instead of objects, or (in languages without functions) you can pass
around small custom objects that are lazily initialized in response to their
single public run method. But these are awkward because the consumer
must explicitly trigger the computation (not, as with Clojure lazy
sequences, trigger it as a side effect of normal access to an element).
Here’s how to create our old friend, all the integers, in Ruby:
1 def integers
2 retval = 0
3 Enumerator.new do | yielder |
4 loop do
5 yielder.yield(retval)
6 retval += 1
7 end
8 end
9 end
Each time the yielder yields a value, the loop is suspended until the next
time a value is called for. So here are the first 6 integers:
1 > integers.take(6)
2 => [0, 1, 2, 3, 4, 5]
The normal Ruby sequence functions continue to be eager rather than lazy.
However, we can add lazy versions to Enumerator like this:
1 class Enumerator
2 def lazy_select(&predicate) # select is like Clojure's filter
3 Enumerator.new do | yielder |
4 self.each do |value|
5 yielder.yield(value) if predicate.(val)
6 end
7 end
8 end
9
10 def lazy_collect(&transformer) # collect is like Clojure's map
11 Enumerator.new do | yielder |
12 self.each do |value|
13 yielder.yield(transformer.(value))
14 end
15 end
16 end
17 end
With those, we can implement our earlier example of taking only the first
factorial greater than 200. First, we make an enumerator based on an array,
using a utility method:
1 > a = [5, 8, 9, 300, 1000].to_enum
2 => #<Enumerator: [5, 8, 9, 300, 1000]:each>
Now we can map and filter, just as in Clojure. In the following, I’m using a
version of factorial that prints how it’s called, so that you can see it’s only
called the needed number of times:
1 > a.lazy_collect { | n |
2 > factorial(n)
3 > }.lazy_select { | n |
4 > n > 200
5 > }.first
6 Factorial 5
7 Factorial 8
8 => 40320
Instead, we can put a pattern for the two vectors into the argument list
itself, naming each point’s constituent parts:
1 (def add-points
2 (fn [ [x0 y0] [x1 y1] ]
3 [ (+ x0 x1) (+ y0 y1) ]))
This code is easier to read because it makes the structure of the data
explicit, rather than something you infer from the functions applied to it1.
For this chapter, I’ll use Clojure’s shorthand for defining functions:
1 (defn factorial
2 [n]
3 (cond (zero? n) 1
4 (= 1 n) 1
5 :else (* n (factorial (dec n)))))
That’s the basic idea of pattern matching: a different parameter list for each
important case of the data. Also notice that a parameter may be a constant
(to be matched exactly) rather than a symbol (to be bound).
Either you have an empty sequence, which is the ending case, or…
… you have a head and a tail. You work on the head and recurse on the
tail.
Here’s an example of how such a function can be written using pattern
matching:
1 (defpatterned count-sequence
2 [ [] ] 0
3 [ [head & tail] ] (inc (count-sequence tail)))
Here’s an example that has two arguments, one a collecting parameter. That
may make the structure easier to see.
1 (defpatterned count-sequence
2 [so-far [ ] ] so-far
3 [so-far [head & tail] ] (count-sequence (inc so-far) tail))
Even though sequences are described with square brackets, they match any
sort of sequence structure:
1 user=> (count-sequence 0 '(:a :b :c))
2 3
You might not want separate parameter lists for the 0 and 1 cases. A
function could be used to match either of them, but there’s a slightly nicer
notation:
1 (defpatterned factorial
2 [(:when (partial > 0) :bind n)] (oops! "No negative numbers" :n n)
3 [(:in [0 1])] 1
4 [n] (* n (factorial (dec n))))
13.4 Summary
Pattern matching isn’t wildly exciting. I’ve described it because its use
reinforces two themes I’ve mentioned throughout this part of the book:
thinking of computation as being about handling different shapes of data,
and removing conditional expressions from view.
Pattern matching also generalizes to generic functions, the topic of the next
chapter.
13.5 Exercises
You can find starting source for these exercises in sources/pattern.clj.
My solutions are in solutions/pattern.clj.
Exercise 1
Change that function so that it can also take a single argument, a sequence
to count:
1 user=> (count-sequence '(:a :b :c))
2 3
Hint: Just add another parameter list and code snippet pair.
Exercise 2
1. You could argue that the structure of a point should be hidden behind
specially-named accessor functions like getX() or getY(). However
that argument applies to both solutions, so is a different topic than the
one in this chapter.↩
14. Generic Functions
In Part 1, we went to great lengths to support object-oriented
polymorphism. We made it possible for the same message to correspond to
many methods, each attached to a different class. You’ve seen nothing
comparable in Clojure, where each function name corresponds to only a
single function.
Until now.
I’m unsure whether I want to include this chapter. I very much like the idea of generic
functions, for reasons partly explained in A Digression on Verbs below. However,
generic functions from the Lisp tradition (including Clojure’s) are quirky and burdened
by their history. (They came from an era of kitchen-sink design and haven’t really
overcome that; they still have remnants of the time when we poorly understood the
tradeoffs between composition, inheritance, and delegation; and I think they’re too
geared toward emulating object-oriented approaches rather than really striking out and
inventing new ones.) Other implementations (that I’m aware of) of what might be
called generic functions are really not that different from the pattern matching of the
last chapter.
So I’m putting it up to the readers. I’ve created a poll so that you can vote on what
should happen to this chapter. I’ll repeat the link at the end of the chapter.
Unless you instruct Clojure differently, it will throw an error if the classifier
function produces a value that matches no specialization. If, however, you
make a specialization for :default, that will be used for any non-matching
value:
1 user=> (defgeneric describe identity)
2 user=> (defspecialized describe 1
3 (fn [n] "one"))
4 user=> (defspecialized describe 2
5 (fn [n] "two"))
6 user=> (defspecialized describe :default
7 (fn [n] "something else"))
8 user=> (describe 18)
9 "something else"
If you read other Clojure code or documentation, know that Clojure’s term for generic
functions is “multimethods”. That’s a common term, but I consider it a historical
hangover from the time when the Lisp world was explicitly trying to pull in ideas and
terminology from the object-oriented world.
1 user=> ::rose
2 :user/rose
Given type metadata, we can declare generic functions that use an object-
oriented style of dispatch:
1 (def oo-style
2 (fn [this & args] (type this)))
3
4 (defgeneric nudge oo-style)
You can nudge an asteroid all you like, but you can’t change their speed.
They’re too massive:
1 (defspecialized nudge ::asteroid
2 (fn [this delta]
3 this))
1 user=> (nudge malse 10000)
2 {:speed 1, :name "Malse"}
Generic functions also support inheritance. For example, notice that both
asteroids and starships have names. Perhaps we should have a generic
description function that applies to all named objects. Here’s how you tell
Clojure of a subtype relationship:
1 user=> (derive ::asteroid ::named)
2 user=> (derive ::starship ::named)
We can give the “base class” a specialization that applies to all ::named
maps:
1 user=> (defgeneric description oo-style)
2 user=> (defspecialized description ::named
3 (fn [this] (str "the " (name (type this)) " " (:name this))))
4 user=> (description malse)
5 "the asteroid Malse"
At this point, I expect that you, my valued readers, are raising a rousing
chorus of “So what?”. Indeed, it doesn’t seem that generic functions, as
presented so far, add much to object-orientation. True, but let me point out
two things.
Code Organization
Object-oriented languages either force or strongly encourage you to clump
together all of a class’s non-inherited methods within a single class
construct. Consider what that means for code understanding. Suppose we
have two classes Foo and Bar, each with methods collide and upgrade. As
is often the case, the two versions of collide share a family resemblance.
They have differences, to be sure, but any notion of “collide” is probably
about two objects that (metaphorically) try to occupy the same
(metaphorical) space. And both upgrade methods are probably about
improving something.
You choose
Is it possible functional languages offer a way out? People are much more
tolerant of ambiguity in verbs than in nouns. We’re perfectly happy with
sentences like these:
… or these:
People who pay for programs want them to do things in the world. The
point of this painting, Delacroix’s “Liberty Leading the People”, is to
motivate (“to stimulate to action”). It both captures (statically) people in
motion but also was intended to motivate the viewers to act on the ideals of
the French Revolution.
Let’s create a generic function called collide. For simplicity, we’ll use a
ridiculous model of collisions. They affect only the :speed of objects:
What should the body of the first specialization look like? Its behavior
depends on the second argument. Asteroids are treated differently than
spaceships. Since putting an if check of other’s type in the body pushes
against the whole point of object-orientation, the usual solution is to send a
message to the other object, with the type of the first argument as part of the
message name:
1 (defspecialized collide ::starship
2 (fn [this other]
3 (collide-with-starship other this)))
Since both asteroids and starships can collide with starships, we have a new
generic function:
1 (defgeneric collide-with-starship oo-style)
2 (defspecialized collide-with-starship ::starship ...)
3 (defspecialized collide-with-starship ::asteroid ...)
And we have to do the same with asteroids, so the whole solution requires
all these definitions:
1 (defgeneric collide oo-style)
2 (defgeneric collide-with-starship oo-style)
3 (defgeneric collide-with-asteroid oo-style)
4
5 (defspecialized collide ::starship
6 (fn [this other]
7 (collide-with-starship other this)))
8
9 (defspecialized collide ::asteroid
10 (fn [this other]
11 (collide-with-asteroid other this)))
12
13 (defspecialized collide-with-starship ::starship
14 (fn [& ships] ...))
15 (defspecialized collide-with-starship ::asteroid
16 (fn [asteroid ship] ...))
17
18 (defspecialized collide-with-asteroid ::asteroid
19 (fn [& asteroids] ...))
20 (defspecialized collide-with-asteroid ::starship
21 (fn [ship asteroid] ...))
This is really complicated. When I was writing sample code for this, I
messed it up several times because the argument order in collide-with-*
is the opposite of collide’s, but the results have to be in the original order.
Moreover, having one object’s collide not do anything except ask the
other object to do something reminds me of the 1901 “Alphonse and
Gaston” comic strip that featured two overly-polite Frenchmen:
It all seems faintly ridiculous. “Collide” is a verb that applies to two objects
of equal status. Wedging it into an object-oriented style, where the receiver
is privileged over other arguments, makes coding awkward and overly
verbose.
Since specializations can match any Clojure value, we can dispatch off the
types of both arguments and return the result as a vector:
1 (defgeneric collide (fn [one two] [(type one) (type two)]))
It’s slightly annoying that we have two specializations for the asteroid-
starship case, but that’s forced by the need to return the results in the same
order as the arguments.
Asking Gus to “just say a few words” was like handing him a knife
and asking him to open a main vein. But hundreds of workers are
gathered in the main auditorium of the Convair plant to see Gus and
the other six [astronauts], and they’re beaming at them, and the
Convair brass say a few words and then the astronauts are supposed to
say a few words, and all at once Gus realizes it’s his turn to say
something, and he is petrified. He opens his mouth and out come the
words: “Well… do good work!” It’s an ironic remark, implying “…
because it’s my ass that’ll be sitting on your freaking rocket.” But the
workers start cheering like mad. They started cheering as if they had
just heard the most moving and inspiring message of their lives: Do
good work! After all, it’s little Gus’s ass on top of our rocket! They
stood there for an eternity and cheered their brains out while Gus
gazed blankly on them from the Pope’s balcony. Not only that, the
workers—the workers, not the management but the workers!—had a
flag company make up a huge banner, and they strung it up high in the
main work bay, and it said: DO GOOD WORK.’
Go off, have fun, and do good work. And thanks for reading.
IV A MITE MORE ON MONADS
(OPTIONAL)
In this section, I’ll walk you through the implementation of two well-known
monads: the Sequence monad (which does what Clojure and Python’s for
does), and the State monad (which lets you pretend that an immutable
language actually has assignment statements, in something like the way that
the Zipper data structure lets you pretend immutable trees are mutable).
I hope that this approach lets you read other descriptions of monads (such
as this and this more easily. I have a different description of the elephant,
and multiple descriptions make it easier for you to integrate knowledge.
15. Implementing the Sequence Monad
Recall that Clojure’s Sequence monad is used like this:
1 (with-monad sequence-m
2 (domonad [a (list 1 2 3)
3 b (list (- a) a)
4 c (list (+ a b) (- a b))]
5 (* a b c)))
That is, there are two different types, or kinds, or shapes, of values at work
in monads. I’m going to call those monadic values and binding values. Here
is where the monadic values are calculated in the above domonad:
And here are where binding values are used:
The body of the domonad is the only place you see a binding value being
calculated (since the result is a number, not a list):
One more monadic value belongs in the picture, the final result:
For a monad to work properly, it must correctly coordinate the binding and
monadic values. Let’s look at what that means by examining the expansion
of domonad.
First notice that the monadic value (1 2 3) is passed as the decider’s first
argument. (Its second argument is a continuation.) Since all deciders in a
domonad’s expansion are the same function, we have a rule for any decider:
For example, our nested monad’s decider function must accept a sequence.
Now notice that the result of the decider is not passed (via ->) to any other
function. It’s the last function in this flow. (All the flows for the remaining
steps are nested inside the first step’s continuation.) That is: the top
decider’s result becomes the result of domonad. And since the result of the
domonad is a monadic value, …
Let’s now look at the interaction of one decider with the decider below it,
using this deeper expansion:
1 (-> (list 1 2 3) ;; <<== first monadic calculation
2 (decider ;; <<== first decider
3 (fn [a]
4 (-> (list (- a) a) ;; <<== second monadic calculation
5 (decider ;; <<== second decider
6 (fn [b] ...))))))
1. The first decider calls a continuation, giving it a binding value (for a).
2. That contination calculates the second step’s monadic value—
something like (-1 1)—and feeds it to the second decider.
3. When the second decider returns (what must be a monadic value), the
continuation immediately returns that to the first decider.
The monadifier’s job is to turn a binding value into a monadic value. In the
Sequence monad, we can use list as the monadifier.
In cases where the monadic values and binding values have the same shape,
the monadifier is just a function that does nothing: identity.
Does it work?
1 user=> (with-monad sequence-monad
2 (domonad [a (list 1 2 3)
3 b (list (- a) a)
4 c (list (+ a b) (- a b))]
5 (* a b c)))
6 ((((0) (-2)) ((2) (0))) (((0) (-16)) ((16) (0))) (((0) (-54)) ((54) (0))))
Although I’ve been sloppy about saying it, we don’t really want the
monadic values to be any old sequences. We want them to be sequences of
numbers. Consider the bottommost use of decider, which might look
something like this:
1 (-> (list 2 0)
2 (decider
3 (fn [c]
4 (monadifier
5 (* a b c)))))))))))
… or we can use the mapcat function, which is shorthand for the above. In
point-free style, it’d be defined like this:
1 user=> (def mapcat (comp (partial apply concat) map))
2 user=> (map list [1 2 3])
3 ((1) (2) (3))
4 user=> (mapcat list [1 2 3])
5 (1 2 3)
15.4 Exercise
My solution is in solutions/sequence-m.clj.
However, this decider will not pass along a nil to its continuation.
1 user=> (with-monad maybe-sequence-monad
2 (domonad [a [1 nil 3]
3 b [-1 1]]
4 (* a b)))
5 (-1 1 nil -3 3)
Notice that there is only one nil in the output. If decider had passed the
nil to the next step, and then that step had checked, seen the nil, and
returned nil instead of (* a b), there would be two nils in the result.
Hint: One way to think about this is that the decider locally (via let)
augments the continuation it’s given to produce a new continuation that
handles nil specially.
Hint:
1 user=> (concat [-1 1] nil [-3 3])
2 (-1 1 -3 3)
3 user=> (concat [-1 1] (list nil) [-3 3])
4 (-1 1 nil -3 3)
Earlier, I said that an advantage of the Sequence monad over Clojure’s for
is that you can combine the Maybe monad and Sequence monad like this:
1 user=> (def combined-monad (maybe-t sequence-m))
The maybe-t monad transformer will in fact work with any well-behaved
monad. To show that it’s not magic, we’ll now extract it from the exercise
solution. I’ll call the extracted function maybe-transform to avoid clashing
with the predefined maybe-t`.
That gives us this equivalent, but more general, definition for combined-
monadifier:
1 (def combined-monadifier
2 (with-monad sequence-m m-result))
The last line looks suspiciously like sequence-m’s decider, which is this:
1 (fn [monadic-value continuation]
2 (mapcat continuation monadic-value))))
At this point, there are exactly two references to the Sequence monad in our
code:
1 (def combined-monadifier
2 (with-monad sequence-m m-result)) ;; <<== here
3
4 (def combined-decider
5 (fn [monadic-value continuation]
6 (let [maybe-ified-continuation
7 (fn [binding-value]
8 (if (nil? binding-value)
9 (combined-monadifier binding-value)
10 (continuation binding-value)))]
11 ( (with-monad sequence-m m-bind) ;; <<== here
12 monadic-value
13 maybe-ified-continuation))))
To start fixing that, let’s first replace the two defs with one let:
1 (let [combined-monadifier ;; <<== here
2 (with-monad sequence-m m-result)
3
4 combined-decider ;; <<== here
5 (fn [monadic-value continuation]
6 (let [maybe-ified-continuation
7 (fn [binding-value]
8 (if (nil? binding-value)
9 (combined-monadifier binding-value)
10 (continuation binding-value)))]
11 ( (with-monad sequence-m m-bind)
12 monadic-value
13 maybe-ified-continuation)))]
14
15 (def combined-monad
16 (monad [m-result combined-monadifier
17 m-bind combined-decider])))
1 (fn [binding-value]
2 (if (nil? binding-value)
3 (combined-monadifier binding-value)
4 (continuation binding-value)))]
And even that code is tractable, if you understand the code for the Maybe
monad’s decider and think very precisely:
1. The Maybe monad makes its decision based on whether a binding
value is nil. (Note that it has to decide based on the binding value, not
the monadic value, because the “shape” of the monadic value is
decided by the source monad. As with the Sequence monad, the
monadic value may hide nils inside it. The Maybe monad is
responsible for protecting the next step from getting a nil binding
value.)
2. If the binding value isn’t nil, it can be passed down in the normal way.
3. But a nil binding value must short-circuit all remaining steps and
return some value compatible with whatever decider is above this step.
That decider is a source monad decider wrapped in Maybe behavior.
That means that the value passed up must be monadified in a way
compatible with the source monad: and that is most easily done by
using the source monad’s monadifier.
I’m not claiming that writing a transformer function is trivial in the sense of
seeing it once and being able to do it flawlessly. I do think it’s like learning
to drive: you fairly quickly get to the point where you can usually perform
effectively without really thinking hard about it.
16. Functions as Monadic Values
People sometimes use the metaphor that a monad’s monadic values are
containers that hold the binding values. That doesn’t apply to all monads
(like the Maybe monad), but it’s an OK way of thinking about the Sequence
monad. Let’s run with that metaphor for this chapter. Except that rather than
using boring values like sequences to hold binding values, we’ll wrap them
inside of functions.
What’s interesting about this is that, since a domonad must return a monadic
value, any use of this monad serves to define a function. So, rather than
calculating results immediately (like all of our previous monads do),
function-monad will, when it’s finished, give us a “frozen” calculation:
The above won’t work yet, because we’ve only defined the monadifier, not
the decider. What should the decider do? It needs to unwrap the monadic
value and pass it to the continuation:
1 (def decider
2 (fn [monadic-value monadic-continuation]
3 (let [binding-value (monadic-value)]
4 (monadic-continuation binding-value))))
That works fine, if by “fine” you mean “a convoluted definition that’s just
like a let except you have to execute the result instead of just using it.”
However, that’s just the start. Because functions can take arguments.
All jobs are functions created by a domonad. Charging starts with a flat
charge of 3 units. Each additional step adds 1. The body is free. (Such a
deal!) Here’s how a two-step “job” is prepared:
1 user=> (def run-and-charge
2 (with-monad charging-monad
3 (let [frozen-step m-result]
4 (domonad [a (frozen-step 8)
5 b (frozen-step (+ a 88))]
6 (+ a b)))))
The job is delivered to the computer operator (Mr. Repl), who puts it into
execution and delivers the result. That looks like this:
1 user=> (run-and-charge 3)
2 {:charge 5, :result 104}
It’s our job to implement charging-monad. What facts do we know?
1. The binding values are numbers. (In the second step, one of them is
added to 88.)
2. The monadic values are functions that take a charge and, when
evaluated, produce a map with :charge and :result fields.
That’s a complicated monadic value, and every piece of it will matter for
developing the monad. As a terser reminder of the shape, I’ll use this
picture:
Now let’s consider the decider. We’ll build it up from its simplest form, the
decider that decides nothing:
1 (fn [monadic-value monadic-continuation]
2 (monadic-continuation binding-value))
We need to get the :result out of the monadic value so we can pass it as
the binding value for the continuation. An application of a monadic value
must look like this:
1 (fn [monadic-value monadic-continuation]
2 (let [enclosed-map (monadic-value charge)
3 binding-value (:result enclosed-map)]
4 (monadic-continuation binding-value)))
I added a charge argument because that’s what this kind of monadic value
requires. But charge is an unbound symbol, meaning it hasn’t been bound
to any value by an enclosing let or function parameter list. We need to
sneak in a binding somewhere. Where?
The decider must return a monadic value. If you ignore the unbound symbol
for a second, you’ll see that it does, because it returns the value of the
continuation, which we know has to be a monadic value. However, nothing
in the decider’s contract says it has to return the continuation’s monadic
value. (Indeed, remember that the Sequence monad doesn’t: it constructs a
new monadic value with mapcat.) Let’s have it make up a new monadic
value to return:
1 (fn [monadic-value monadic-continuation]
2 (fn [charge] ;; <<== Return a new function, not the continuation's
3 (let [enclosed-map (monadic-value charge)
4 binding-value (:result enclosed-map)]
5 (monadic-continuation binding-value))))
That has part of the right shape, namely the top half of this:
The bottom half is wrong, though, because our new wrapping function
doesn’t return a map. It returns the results of the continuation, which is a
function—specifically, a monadic value.
Besides, we still need a place to increment the charge. How about passing
an incremented version of it to the continuation’s return value?
1 (fn [monadic-value monadic-continuation]
2 (fn [charge]
3 (let [enclosed-map (monadic-value charge)
4 binding-value (:result enclosed-map)]
5 ( (monadic-continuation binding-value) (inc charge))))) ;; <<==
That way, we get to increment, and we also extract the result map from the
monadic value that the continuation returns.
16.3 Exercises
Exercise 1
Exercise 2
Exercise 3
Move the inc of the charge from the decider to the monadifier. What affect
does this have on the results?
Next, let’s provide more flexibility to the steps. Right now, they all boringly
freeze their results:
1 user=> (def run-and-charge
2 (with-monad charging-monad
3 (let [frozen-step m-result]
4 (domonad [a (frozen-step 8)
5 b (frozen-step (+ a 88))]
6 (+ a b)))))
But the steps don’t have to use frozen-step (the monadifier). They can use
any function that produces the right shape. How about a function that makes
the state available to later steps? That would look like this:
1 user=> (def calculation-with-initial-state
2 (with-monad state-monad
3 (let [frozen-step m-result]
4 (domonad [a (get-state)] ;; <<==
5 (- a)))))
6
7 user=> (calculation-with-initial-state 1)
8 {:state 1, :result -1}
What does get-state look like? It takes no arguments and has to return a
monadic value like this:
It need only shift the state that it’s given to be the :result part of the map.
That’s what becomes bound to the step’s symbol and is thus available to all
the later steps. I’ll show both the monadifier and get-state to make the
two easier to compare.
1 (def monadifier
2 (fn [result]
3 (fn [state]
4 {:state state, :result result})))
5
6 (def get-state
7 (fn []
8 (fn [state]
9 {:state state, :result state})))
If we’re going to write functions that do unusual things to the :result key,
perhaps we should do the same for the :state key. As it stands, the decider
allows no changes to the state, in that it ignores the :state key provided by
the monadic value:
1 (fn [monadic-value monadic-continuation]
2 (fn [state]
3 (let [enclosed-map (monadic-value state)
4 binding-value (:result enclosed-map)] ;; <<== :state key ignored.
5 ( (monadic-continuation binding-value) state))))]))
The State monad can be the basis for a number of other monads. For
example, a state monad that takes a sequence and adds values to it can be
used for logging. Haskell builds on the State monad to do IO in a language
more rigorous about immutability than Clojure is. That is, in Clojure, the
following two expressions cause immediate input or output:
1 user=> (slurp "/etc/passwd")
2 "##\n# User Database\n# \n# Note that this file is consulted directly ..."
3 user=> (println "hello")
16.5 Exercises
You can start from the source in sources/function-monads.clj. (You can
find my solutions in solutions/function-monads.clj.
Exercise 4
Exercise 5
For fun, I once wrote a small program to run on an emulator for the PDP-1,
a computer that had only one register through which you could make
changes to the state of memory1. It was hard. From this, I conclude that if
you’re going to have state, you should have lots of it. Therefore, change the
State monad so that state is represented by a map. The stateful functions
should take a keyword that names the “variable” they work with:
1 (get-state :a)
2 (assign-state :b 3)
3 (transform-state :c inc)
All functions should only return the state of their variable argument. (That
is, transform-state should return the old value of :c in the state, not the
whole state.)
Write an example using all three. If I’m lucky, you’ll experience a vague
discomfort with having to think about both old and new values of variables.
If so, you’ve been bitten by the immutability bug.
As a final bonus, we’ll peek at some of the surprising things languages like
Ioke and Self turn into objects.
17. The Class as an Object
In Part 1, we made new objects using a function named make. I don’t like it.
I’d rather send a :new message to the class, like this:
1 user=> (def my-point (send-to Point :new 1 2))
2 user=> (send-to my-point :class-name)
3 Point
1 (def Point
2 {
3 :__own_symbol__ 'Point
4 :__superclass_symbol__ 'Anything
5 :__class_methods__
6 {
7 :origin (fn [class] (make Point 0 0) ;; New
8 }
9 :__instance_methods__ {...}
10 })
Notice that class methods take an argument that plays something like the
role of this in instance methods. class can be used to call one class
method from another. There may or may not be “class variables” that play
the same role as instance variables.
I’m not satisfied by this solution. Although the external syntax for sending
messages to classes is the same as for instances, that’s just surface: the
distinction is maintained in the implementation. We can do better.
If classes themselves had classes to their left, we could use the same rule.
Let’s call those “classes of classes” metaclasses. Here’s what that looks
like:
Sending a message to a class provokes a lookup
Here are some examples of how the behaviors you’re used to still work:
1 user=> (send-to Anything :new)
2 {:__class_symbol__ Anything}
3 user=> (def point (send-to Point :new 1 2))
4 user=> point
5 {:y 2, :x 1, :__class_symbol__ Point}
6 user=> (send-to point :class-name)
7 Point
8 user=> (send-to point :shift 1 2)
9 {:y 4, :x 2, :__class_symbol__ Point}
I think that’s neat: classes as objects themselves! How have we gotten here,
though?
That was a bit too minimal, so we added a metadata key that named a class:
Since the global functions that used this really didn’t look much like
methods, we began moving them into objects. First, we added them to the
objects themselves:
We created a simple dispatch function that knew how to convert from
messages (keywords) to the methods (functions) that were nested inside the
object.
Then we decided that, since we already had the notion of a class that named
all similar instances, we’d be better off putting the instance methods there
(converting the class from a symbol to a map itself):
Because of our implementation, we needed the class to know its own name
in order to create instances:
We next implemented inheritance by having a class point “up” to its
superclass:
Finally, we decided that classes might as well be real objects themselves,
which meant they too should have classes:
So: what’s the difference between a class and other objects? All objects
have a :class-symbol. Classes have, in addition, an :own-symbol (which is
used by :new to give instances their :class-symbol values), a
:superclass-symbol, and a map of :__instance_methods__.
The picture above matches Ruby’s object model, except that we’re giving
metaclasses explicit names. Ruby doesn’t (though you can still get to them
if you need to). So this part of our object model is perhaps closer to
Smalltalk’s. That language gives metaclasses explicit names, emphasizing
that they themselves are objects (and classes).
There are still some flaws in our model. You’ll address them in the next set
of exercises.
17.4 Exercises
You can find the starting source for these exercises in sources/class-
object.clj. My solutions are in solutions/class-object.clj.
Exercise 1:
Make it so that the following message send produces the same error:
1 user=> (send-to point :the-name-of-no-method 1 2)
2 Error A Point does not accept the message :the-name-of-no-method.
Exercise 2:
… write code that shows what kind of difference the superclass makes.
Exercise 3:
Write the code to set the :__class_symbol__ values of the two metaclasses,
then demonstrate the difference the change made.
Hint: The above are reasonable messages to send. That means that any
metaclass’s class must be a subclass of Anything.
Exercise 4
If you look back to the previous picture in this chapter, you’ll see a class
hierarchy. Revise it to include the changes you’ve made in these exercises.
My solution follows.
18. The Class That Makes Classes
In the previous chapter’s exercises, you produced this class diagram:
In this chapter, we’ll modify it by adding a class that builds other classes.
We’ll do that in two stages.
(Note: It’s more common to name this class Class, but—as with Object—
Clojure reserves that to refer to the core Java class. So I reluctantly misspell
it.)
Here’s a first version of what that class structure would look like.
Klass and MetaKlass fit into the diagram just as the earlier Point and
MetaPoint did—they have the same superclass and class arrows. But
there’s an important difference: MetaKlass has a :new method. Therefore,
as shown below, the (send-to Klass :new 'Point ...) example above
would dispatch to that :new, not the one in MetaAnything.
The code for this new version of :new (which you’ll see in the next two
sections) generates the familiar Point and MetaPoint classes:
Note the implication of this: manually defining Anything, MetaAnything,
Klass, and MetaKlass bootstraps the object system to the point where we
can use it to create all remaining classes.
Once Klass creates Point and MetaPoint, it has nothing more to do with
them. Indeed, they have no pointers to it. So the dispatching to create a new
Point is as before:
And the resulting class structure is as before (except for the two new Klass
classes):
Now for the implementation.
It takes four arguments: the name of the class (from which the name of the
metaclass will be derived), the name of the superclass, a map from message
names to instance methods, and a map from message names to class
methods.
basic-object
metasymbol
Since our convention for metaclasses is that they always begin with “Meta”,
let’s make a function that creates a symbol naming a metaclass from a
symbol naming a class:
1 (def metasymbol
2 (fn [some-symbol]
3 (symbol (str "Meta" some-symbol))))
That way, we won’t always have to pass metaclass and class names around
together.
basic-class
basic-class makes a basic object that has a class’s three additional bits of
metadata, as shown in this picture:
Because we’ll have to use basic-class directly (not via send-to Klass
:new) when creating Anything, Klass, and their metaclasses, I’ll make its
uses more readable by adding dummy keywords to the argument list:
1 user=> (basic-class 'Point ; Name of new class
2 :left 'MetaPoint ; Its class
3 :up 'Anything ; Its superclass
4 {:x :x}) ; Instance methods
5 {:__class_symbol__ MetaPoint
6 :__superclass_symbol__ Anything,
7 :__own_symbol__ Point,
8 :__instance_methods__ {:x :x}}
install
Because (send Klass :new ...) creates both a class and its metaclass, we
can’t type something like this:
1 user=> (def Point (send-to Klass :new 'Point ...))
The :new method will have to create two bindings (Point and MetaPoint).
It’ll use an install method, something like this:
1 ...
2 :new
3 (fn [class-symbol superclass-symbole instance-methods
4 class-methods]
5 ...
6 (install (basic-class class-symbol...))
7 (install (basic-class superclass-symbol...))
8 ...)
9 ...
You haven’t seen *ns* before. It has the current namespace as its value. A
namespace is like a package or module in other languages: it makes name
clashes less likely. In this book, you’re doing all your work in the user
namespace.
So here’s install:
1 (def install
2 (fn [class]
3 (intern *ns* (:__own_symbol__ class) class)
4 class))
I’m returning the class argument because that will later be a convenient
way to make the :new method return the class it created.
:new
That given, here’s :new:
1 (install (basic-class 'MetaKlass,
2 :left 'Anything,
3 :up 'MetaAnything,
4 {
5 :new
6 (fn [this
7 new-class-symbol superclass-symbol
8 instance-methods class-methods]
9 ;; Metaclass
10 (install
11 (basic-class (metasymbol new-class-symbol)
12 :left 'Anything
13 :up 'MetaAnything
14 class-methods))
15 ;; Class
16 (install
17 (basic-class new-class-symbol
18 :left (metasymbol new-class-
symbol)
19 :up superclass-symbol
20 instance-methods)))}))
You can find the complete source for the new object systemin
sources/klass-1.clj. In rough outline, it looks like this:
1 ;;; The two class/metaclass pairs from which everything else can be built
2
3 ;; Anything
4 (install (basic-class 'Anything ...))
5 (install (basic-class 'MetaAnything ...))
6
7 ;; Klass
8 (install (basic-class 'Klass ...))
9 (install (basic-class 'MetaKlass ...))
10
11 ;;; The remaining predefined classes
12
13 ;; Point
14 (send-to Klass :new
15 'Point 'Anything
16 {...}
17 {...}
The rest of this chapter will add the code in sources/klass-2.clj to the code in
sources/klass-1.clj.
However, that’s still not a complete solution, because the class above the
now-invisible MetaPoint is the also-invisible MetaAnything. So searching
up a superclass hierarchy for the first visible class would find… Anything.
Which is no more useful than MetaPoint, since everything is an Anything
and we want a name for what’s special about Point (which is that it’s a
class).
You’ll see that there’s no way for the dispatch function to get to
MetaAnything and find the :new there. :new needs to be moved into the
path. Putting it in MetaPoint would be wrong, since :new should be
behaviors shared by all classes. Putting it in Anything is inappropriate,
since that would mean :new could be sent to an instance of Point, not just
the Point class. (A :new sent to a Point instance would look left to Point
and then up the superclass link to Anything.)
The right place to put it is in Klass, which leads to this appealingly compact
situation:
The Klass pair of classes is responsible for all “newing” of objects. Klass
acts (via the superclass link) as a metaclass for all objects-that-are-classes.
Therefore (send-to Point :new), (send-to Alphabet :new), and so on
all invoke Klass’s instance method to create new instances. Like so:
What happens when you send :new to Klass? The same thing as always: a
look to the left and then (if necessary) up. In this case, the look to the left is
enough:
That is:
1. In the initial system, all of Anything, Point, and Klass are classes:
they are all capable of creating instances in response to :new.
2. Klass has its own version of :new, one that knows how to create other
classes.
3. The proper definition of what it means to be a class is that it is an
object whose metaclass is a descendent of Klass. (Klass is a
descendent of itself because of the circularity between it and its
metaclass.)
You can find the three superclass changes and the moved :new in
sources/klass-2.clj.
It’s interesting that it took a lot of text and pictures to describe and justify a
change that amounted to moving one method and changing three arrows.
Things can get pretty subtle up near the top of the class hierarchy.
18.6 Exercises
My solution is in solutions/klass.clj.
Exercise 1:
Implement it.
Exercise 2:
Note that the order of symbols is the opposite of the one lineage supplies.
Be sure to test your solution against both built-in classes and ones freshly
created with (send-to Klass :new).
Hint: You can always add metadata to classes. You can even add it after the
class has been created, like this:
1 (def Point (assoc Point :__new_metadata__ "value"))
Exercise 3:
Consider a class diagram for the trilobite, the favorite fossil of many a
discerning enthusiast1:
(I’ve simplified the class diagram by removing class Klass.)
If you have a set of trilobites, you can order them according to the number
of lenses in an eye (which ranges from one to thousands). That means that
the Trilobite class should provide methods like these: :<, :<=, :=, :>, :>=,
and :between?.
When thinking about those methods, you might notice that they can all be
defined in terms of a single one, :<=>, which Ruby calls “the spaceship
method” (because it looks a little like a flying saucer). The spaceship
method returns -1 if this is smaller than the single argument, 0 if they’re
equal, and 1 if the argument is greater.
Imagine repeating those six definitions in every class that wants a “natural
order” for instances. It would be far better to have such classes define only
:<=> and get the other methods for free.
Ruby accomplishes that through its notion of modules. Modules are like
classes, except that you can’t make instances of them. Instead, you “mix
them in” or “include them into” classes. Doing so adds the module’s
methods to the class. For example, here’s how a Ruby Trilobite class
could include the Comparable module to get the six additional methods:
1 class Trilobite
2 def <=> [other]
3 self.eyes.count <=> other.eyes.count
4 end
5
6 include Comparable
7 end
As you’ll see shortly, the methods contained in modules are found by the
same sort of “look left, then up” pattern. But that makes
:__class_symbol__ and :__superclass_symbol__ bad names. Sometimes
what’s to the left is a module, not a class. The object above a module isn’t
usefully described as its superclass.
Because of that, I’m going to change the keywords used in the code:
Old New
:__class_symbol__ :__left_symbol__
:__superclass_symbol__ :__up_symbol__
For the same reason, I’ll be dropping the labels “class” and “superclass”
from arrows in diagrams.
Finally, since the objects at the ends of those arrows might be either classes
or modules, we need some collective term. From now on, we’ll talk of
method holders. Functions that used to have names like class-symbol-
above will be renamed method-holder-symbol-above.
Let’s step through what happens when a Trilobite instance receives the :>
message. As always, we look left and up, finding the version in
Komparable:
Therefore, the module sends a different method to the same this (the same
instance). Like this:
The module is behaving just like a class.
(Notice that module inclusion doesn’t affect metaclasses. For that reason,
I’ll leave them out of diagrams from now on.)
But that brings with it a second problem: now we can’t change Komparable
and have those changes be seen by Trilobite and Lie. In languages with
repls (like Clojure and Ruby), being unable to redefine classes, functions,
and other such values is bad form.
Therefore, Ruby adds a layer of indirection. :include doesn’t put a module
in the superclass chain. Instead, it constructs a small stub that points both
upward and to the left:
Alternately, you can think of each stub being recursively replaced by what it
points to on its left. That would look like this:
19.4 Adding Module to the class structure
Modules like Komparable have to be created. It seems sensible to create
them like this:
1 (send-to Module :new 'Komparable
2 {:= (fn [this that] ...)
3 :> (fn [this that] ...)
4 ...})
One difference between the two pairs is that classes can create instances but
modules cannot. (The Module object does not contain a :new method.) A
similarity (not yet shown in the diagram) is that both classes and modules
respond to the :include message. We can tidily accommodate both those
facts by making Klass a subclass of Module:
Let’s review what that means:
Message Behavior
(send-to Klass :new
MetaKlass makes a new class
'Trilobite)
(send-to Trilobite
Klass makes a new instance
:new)
(send-to Module :new MetaModule makes a new
'Komparable) module
(send-to Komparable Invalid, since Module has no
:new) :new method
(send-to Trilobite Module makes new methods
:include ...) available to Trilobite
(send-to Komparable Module makes new methods
:include... ) available to Komparable
There are more arrows to adjust. MetaKlass used to have an upward link to
Klass, which is appropriate because metaclasses are classes. It now has an
upward link to MetaModule. To preserve MetaKlass’s status as a class, and
to make MetaModule a class itself, MetaModule should trace upward to
Klass:
In effect, we’ve divided the behavior of Class into two parts: Class and
Module, and put Module just above Class in the hierarchy:
So that’s the plan. You’ll do the work. In the exercises, you’ll add Module to
the hierarchy, implement its :new and :include methods, and modify the
dispatch function to take module stubs into account.
19.5 Exercises
As I noted earlier, I’ve changed the names in the source now that we have
both classes and modules. As a result, this is what a Trilobite instance
looks like:
1 user=> (send-to Trilobite :new 3)
2 {:facets 3, :__left_symbol__ Trilobite} ; :__left_symbol__ now, not :__class_sym\
3 bol__
Exercise 1
Exercise 2
This is similar to (send-to Klass :new...) in that it takes the name of the
module and a map of methods. It’s different because it doesn’t take a
superclass name or a map of class methods. (Since there are no class
methods, there’s no need to create a metaclass for the new module.)
Hint: To think about what’s to the left of a module, consider: that object is
responsible for defining the method that responds to the following message:
1 (send-to Kuddlesome :include SomeOtherModule)
Exercise 3
When you’ve completed this exercise, you’ll be able to type the following:
1 user=> (:__up_symbol__ Trilobite)
2 Anything
3 user=> (send-to Trilobite :include Kuddlesome)
4 user=> (:__up_symbol__ Trilobite)
5 Kuddlesome73
6 user=> (:__up_symbol__ Kuddlesome73)
7 Anything
8 user=> (:__left_symbol__ Kuddlesome73)
9 Kuddlesome
Make sure that this behavior also works when you include a module in a
module.
Hint: Use the install function to install a modified class or new module.
Exercise 4
Add a new case to lineage-1 that handles module stubs. Once that’s done,
inheritance, :ancestors, and :class-name should all work.
Hint: You first need a way to identify when a symbol is bound to a module
stub (rather than a class or module).
Although both outside and inside bind the same variable, my-number,
they create different bindings that cannot affect one another. Even though
inside is called from within the body of outside’s let expression, which
has bound my-number, the binding of my-number that inside’s first println
sees is that of the earlier def, the one before and outside of both outside
and inside.
This happens because let (and function parameter lists) are lexically
scoped. The bindings they produce follow visibility rules that are based on
nesting levels in the text of the program.
What follows is a picture of these lexical scopes. The outermost box is the
global scope. The gray areas are scopes created by let expressions. The
function parameter lists also introduce scopes, but they don’t bind anything.
Because of that, I’m not showing them or their code.
The flow of control is from the top down: first the def is executed, then
outer makes its binding. Within the scope of that binding, it calls inner,
which makes its own binding:
Lexical scoping
We’re so used to lexical scoping that the idea inside could see into
outside seems crazy. That’s interesting because, if you look at the history
of programming languages, it’s amazing how long it took many extremely
smart people to understand lexical scoping. Truly it’s wonderful that what
was so hard for the giants of the past has become “intuitively obvious” to us
pygmies today.
If it took them a long time to understand lexical scoping, what did they use
instead? It’s called dynamic scoping. Think of it as if there is a single stack
of bindings for each symbol (rather than multiple bindings that happen to
refer to the same symbol). In the original Lisps, let expressions and
function application would push new values onto that stack. Any reference
to the bound value of a symbol, anywhere in the program, would get the top
value. In this regime, the earlier example has a different picture:
Dynamic scoping
With the exact same code, dynamic scoping produces a different result:
1 oldlisp=> (outside)
2 starting value of my-number: 100 ;; not 1
3 rebound value of my-number: 101 ;; not 2
In modern Lisps, like Clojure, lexical scoping is the default. But many of
them support dynamic scoping as an alternative. In Clojure, you can make a
symbol available for dynamic scoping like this:
1 user=> (def ^:dynamic this nil)
let continues to use lexical scoping, but there’s a separate form that
introduces a dynamic scope:
1 user=> (binding [this {:value 33}]
2 (:value this))
3 33
And that binding form allows us to have functions with an implicit this:
1 user=> (def increase-by-a-lot
2 (fn [] (assoc this
3 :value (* 2 (:value this)))))
4
5 user=> (binding [this {:value 33}]
6 (increase-by-a-lot))
7 {:value 66}
An implicit this requires only one substantive change to our code. The
function apply-message-to must bind this instead of passing it in. Here’s
the old version:
1 (def apply-message-to
2 (fn [method-holder instance message args]
3 (let [method (message (method-cache method-holder))]
4 (if method
5 (apply method instance args) ;; <<== old
6 (send-to instance :method-missing message args)))))
1 :x :x
2 :y :y
Another slight annoyance is that I used to name the first argument of class
methods class:
1 :origin
2 (fn [class] (send-to class :new 0 0))
But now we have to use the implicit this. (Because class methods are
instance methods of classes.)
1 :origin
2 (fn [this] (send-to this :new 0 0))
That calls the shadowed some_method, giving it the same arguments as the
shadowing method got. It’s the same as this:
1 def some_method(arg1, arg2)
2 ...
3 super(arg1, arg2)
4 ...
5 end
Notice that Ruby’s super doesn’t let you send an arbitrary message to the
superclass. That is, suppose you have this class structure:
… and you have a Trilobite object executing its method1. There is no use
of send-super that allows that method1 to invoke Arthropod’s method2. It
can only delegate to Arthropod’s method1.
In our object system, we’ll have two distinct functions, send-super and
repeat-to-super. Here’s the first:
1 :calculate
2 (fn [x y z]
3 (send-super x y))
Notice that each method holder has a :print-up method. Suppose each of
them prints the name of the holder and then calls send-super. Then here
would be the result of calling :print-up on a Trilobite instance:
1 In Trilobite
2 In Cuddlesome
3 In Squamous
4 In Comparable
5 In Anything
6 Error: No superclass method `:print-up` above `Anything`.
20.4 A design
Consider the example that ended the next section. When :Cuddlesome uses
send-super, the search for the next method has to start with Squamous. That
suggests we need a dynamically-bound symbol that records the method
holder that holds the currently-executing method. We’ll call that holder-
of-current-method. A send-super search must always start at the method
holder above it.
20.5 Exercises
You’ll start with the sources in sources/dynamic.clj. My solutions are in
solutions/send-super.clj.
I expect you’ll find your solution uncomfortably messy. Mine is too. Don’t
worry about that—it’ll be fixed in the next chapter.
Exercise 1
Exercise 2
You can use the following class to confirm the bindings. It’s defined in
sources/send-super-exercises.clj.
Exercise 3
Exercise 4
Implement send-super.
Exercise 5
:target:
The receiver of the message. Because method writers will want to keep
using this, that symbol will be made a synonym for this value.
:name:
The message name as a keyword. The second argument to send-to.
:args:
A sequence of the arguments sent with the message.
:holder-name:
The method holder wherein a match for the message name was found.
send-super and repeat-to-super should start their search above this
value.
For this chapter, I’m going to call such a map an active message, and I’ll
say that such a message contains the information needed to activate a
method (that is, apply it to the arguments). The terminology isn’t
particularly important: I’m mainly using it because you’ll be basing your
exercise solutions on some 350 lines of code, and I want the code you’ll
need to change to stand out.
I’m using “earmuffs” in the name because that’s the Clojure convention. I
violated that convention in the previous chapter because I thought *this*
would look too weird.
21.3 Implementation
Here are brief descriptions of each of the important functions. I expect this
will be dull to read, so you might just want to use it for reference during the
exercises.
Exercise 1
Hint: You can make a map with (apply hash-map [:key1 "value1"
:key2 "value2"]).
Exercise 2
After that fails, try to come as close to a real message-send as you can
without getting into an infinite loop.
Hint: Don’t forget to bind this for the call to ActiveMessage’s method.
Exercise 3
1 (def repeat-to-super
2 (fn []
3 (activate-method (using-method-above *active-message*))))
4
5 (def send-super
6 (fn [& args]
7 (activate-method (assoc (using-method-above *active-message*)
8 :args args))))
Show that, even though ActiveMessage is intimately tied into the internals
of the system, it too can send messages. You’ll do that by editing :move-up.
… to this:
1 ... (send-to this :holder-name) ... ; use a getter method
Next, split move-up into more than one method. It’s too big.
Exercise(ish) 5
I think you can see where this is going. The game is to find pieces of the
system that can be moved into classes, creating new classes along the way.
You win the game by having the smallest number of free-standing
functions.
I won’t take you any further down this path, but if this is the kind of game
you like, I encourage you to make some more moves on your own.
A test suite will help you. If you’re using Leiningen (lein repl) and install
the Midje Leiningen plugin, you can run a halfway-decent set of tests for
the object model like this:
1 734 $ lein midje solutions.ts-message-class-continued
2 All claimed facts (68) have been confirmed.
To make these tests use your solution, change the following line in
test/solutions/ts_message_class_continued.clj.
Exercise 6
While the other readers are working on the previous exercise, we’ll look at
what else you can do with ActiveMessage.
Exercise 7
Give each ActiveMessage a link back to the message that came before it.
argument:
In this book, I reserve “argument” for the actual values to which a
function is applied. I use parameter for the symbols in a function
definition’s parameter list.
atom:
In Clojure, an atom is a “container” for a value. The atom can be
mutated to hold a different value (not to change the value within it).
The change is made by fetching the current value, passing it to a
function, and storing the function’s return value. If two threads attempt
to modify the atom at the same time, Clojure guarantees that one will
complete before the other begins.
binding:
A binding associates a symbol with a value.
binding value:
In this book, used to contrast with monadic values. A monad accepts a
monadic value, processes it, and then binds the resulting binding value
to a symbol to make it available to later steps.
class:
A class describes a collection of similar instances. It may describe the
data those instances contain (by naming instance variables). It may
also describe the methods that act on those instance variables.
class method:
A class method is executed by sending a message to a class, rather
than to an instance. In languages like Ruby and the embedded
language of Part 1, classes are instances, so when you send a message
to an instance that happens to be a class, you get an instance method of
that class object, which we call a class method. That is, there’s no
implementation difference between a-point.foo and Point.new. See
The Class as an Object chapter.
classifier function:
The classifier takes the arguments to a generic function and usually
converts them into a small number of values that are used to select a
specialized function.
closure:
A function that can be applied to arguments but that also has
permanent access to all name/value bindings in its environment at the
moment of function creation. As such, it can make use of named values
defined “outside” itself, even after the names that refer to those values
cease to do so.
collecting parameter:
In a recursive function, a collecting parameter is one that is passed a
closer approximation to the final solution in each nested recursive call.
See the explanation in the book.
constructor:
A constructor creates an instance based on the information in a class.
The resulting instance is “of” that class.
continuation:
During a computation, the continuation is a description (in the form of
a function) of the computation that remains to be done.
continuation-passing style:
Writing a computation as the calculation of one value that is then
passed to a function that represents the continuation of the
computation. See the description in the text.
dataflow style:
A programming style that emphasizes data flowing through a series of
functions and being transformed at each stage.
depth-first traversal:
A tree traversal in which, if the traversal has a choice whether to go
down first or right first, it chooses “down”.
destructuring binding:
When a sequence is passed as an argument, destructuring binding lets
you bind parameter names to elements of the sequence without having
to bind the whole sequence to a name and then pick it apart with code.
dispatch function:
When a name can refer to more than one function, the dispatch
function decides which function to apply by examining the argument
list.
double dispatch
A kludge required in conventional object-oriented programming, used
when the correct behavior depends on both this and another object.
See the discussion in the book.
duck typing:
A way of defining class relationships used in languages without static
types. Inspired by the saying “If it walks like a duck and talks like a
duck, it’s a duck”. When duck typing, you don’t define one class as
depending on another’s type but rather on particular messages it
responds to. It differs from (say) Java’s interfaces in that the sets of
messages aren’t distinct named entities in the program, but rather
implicit groups, one for each purpose.
dynamic scoping:
When a symbol is evaluated to find its bound value, the binding that’s
used is the one most recently evaluated during execution of the
program. The position of the binding code in the program’s text is
irrelevant. Contrast to lexical scoping.
eager evaluation:
The opposite of lazy evaluation. Computation is performed
immediately, rather than as values are demanded.
encapsulation:
Making the binding between a symbol and a value invisible to code
outside a function or object boundary.
environment:
The environment collects all symbol/value bindings in effect at a
particular moment.
evaluator:
An evaluator converts a data structure, usually obtained from the
reader, into a value. See the explanation in the text. Clojure’s evaluator
is named eval.
function:
In general terms, a function is some executable code that is given
arguments and produces a value. In Clojure, a function is specifically a
closure.
future:
A future converts a computation into a value. A computation wrapped
in a future executes on a different thread. If the value of the future is
ever referenced, and the computation is not finished, the referencing
thread is paused until the value is computed.
generic function:
In conventional object-oriented programming, the dispatch function
looks only at the type of the object given as the implicit “this”
argument. Generic functions provide a different strategy, in which the
dispatch function is user-provided and can use any argument. In
Clojure, generic functions are defined with defmulti.
Generic functions encourage a verb-centered way of thinking about the
world: there are actions that can apply very broadly. The specifics of
an action depends on some properties (determined at runtime) of the
values it’s applied to.
global definition:
In a global definition, a function is bound to a symbol using def. Such
a function can be used by any other function in the namespace.
Contrast with local definition.
higher-order function:
A function that either takes a function as an argument or produces a
function as its return value.
immutability:
In Clojure, data structures cannot be modified once created. Within
functions and let forms, the association of a symbol to a value cannot
be changed once made (because there is no assignment statement in
Clojure).
instance:
Synonymous with object, but emphasizes that the instance is one
representative of a class (from which it is instantiated).
instance method:
The method applied in response to a message sent to an instance. Used
when a distinction between instance methods and class methods is
useful. More usually, an unqualified “method” is used.
instance variable:
The data an instance holds can be thought of as a collection of
name/value pairs. “Instance variable” can refer to the name part or to
both parts. For example, “initialize the instance variable to 5”
associates a value with the name. In Clojure and other languages with
immutable data, instance variables don’t ever vary.
instantiation:
Creating an instance by allocating space, associating runtime-specific
metadata with it, and then calling a class-specific function to initialize
instance variables.
keyword:
A clojure datatype, written like :my-keyword. Keywords evaluate to
themselves and are often used as the key in a map. Keywords are
callables.
lazy evaluation:
In a fully lazy language, no computation is performed unless some
other computation demands its results. In effect, evaluation is a “pull”
process, where the need to print some output ripples “backward” to
provoke only those computations that are needed. Clojure is not fully
lazy, but it has the lazyseq data structure, which is.
lazy initialization:
In an object-oriented language, an instance variable is lazily initialized
if its starting value is only calculated when some client code first asks
for it.
lazyseq:
A Clojure sequence that uses lazy evaluation.
lexical scoping:
The most common sort of binding in modern programming languages.
When there is more than one binding for a symbol, evaluating that
symbol uses the closest enclosing binding in the text of the program.
Nothing in the execution of the program can change which binding is
used. Contrast to dynamic scoping.
list:
A clojure sequence that has the property that it takes longer to access
the last element than the first. Lists are used both to hold data and to
represent Clojure programs.
local definition:
A function definition that is either used immediately (as in ( (partial
+ 1) 2) and so has no name, or whose name is given in a let binding
or a function’s parameter list. Whereas a function with a global
definition can be used by any function in the namespace, a local
definition can be “seen” only within the body of its let or function
definition.
macro:
A function that translates Clojure code into different clojure code. The
transformed code is evaluated in the normal way. Macros are a way of
inventing your own special forms.
map:
As a noun, an unordered collection of key/value pairs, like a Java
HashMap or a Ruby Hash. Maps are callables.
As a verb, a function that applies a callable to each element of one or
more collections. The return values are collected together and returned
in a lazyseq.
message:
A message is the name of a method. When functions are used as
methods, we use the metaphor that the program sends a message and
arguments to an object.
metaclass:
A class that describes a class in the same way that a class describes an
instance. Metaclasses store the methods invoked in response to a
message sent to a class object.
metadata:
Data about data. An example in this book is the pointer from an
instance to its class, which the dispatch function uses when deciding
which method to apply.
method:
A method is a function with a (usually) implicit “this” or “self”
argument that refers to an instance of a class. Metaphorically, the
method is invoked when a message of the same name is sent to the
instance.
mock object:
A mock object is used to test whether classes use their collaborating
classes correctly. It stands in for one of the object-under-test’s
neighbor objects. The test programs the mock to expect the object-
under-test to send it specific messages. If the mock object is not sent
those messages, the test fails.
module:
In Ruby, a module is a class-like object that can be placed in the
inheritance chain of a class
monad:
A set of functions that describes how to separate the steps of a
computation from what happens between those steps.
monad transformer:
A function that takes one monad as its argument and produces another
monad that has the properties of the argument monad plus different
monad.
monadic function:
A function that takes a single binding value and converts it into a
monadic value.
monadic value:
The type (or shape) of value that a monad operates on. A monad
accepts monadic values, may or may not do something to them, and
provides the results to a computational step as a binding value.
multimethod:
A synonym for generic function.
multiple inheritance:
In multiple inheritance, an object can have more than one direct
superclass, so its ancestors could form a complicated graph (with
classes appearing more than once) rather than a simple sequence.
namespace:
Namespaces are Clojure’s equivalent of packages or modules in other
languages: a way of restricting the visibility of names to other parts of
a program. Roughly speaking, a namespace corresponds to a file.
For purposes of this book, a namespace is a map from symbols to
values. (The reality is slightly more complicated.) There are functions
that give one namespace access to values in another (by altering the
client namespace’s own map).
object:
Conventionally, encapsulated mutable state. Because of a class
definition, certain methods can be applied to that state.
ORM:
Object-relational mapping. A library or framework that stores objects
in a relational database and can reconstruct those objects later.
override (a method):
In an object-oriented language, a method defined in a subclass
overrides a method with the same name in a superclass. In that case,
the dispatch function applied to an instance of the subclass will pick
the subclass version.
parameter:
In a function definition, the parameter list is a vector of symbols.
During function application, those symbols are replaced with the
corresponding values in the argument list.
partial application:
Recasting a function of n arguments as one of n-m arguments, where
the m arguments are replaced by constants. In Clojure, (partial + 3)
produces a function that adds three to its argument. Often also called
“currying”, though that term is strictly incorrect.
point-free definitions:
Functions that are created without mentioning their parameters.
Creation is done with higher-order-functions.
polymorphic:
When one name associated with potentially many functions (or
methods). The dispatch function uses the argument list (perhaps
including the receiver of a message) to decide which function to apply
to the arguments.
printer:
The printer converts the internal representation of data to output
strings. See the explanation in the text.
reader:
The reader converts text into Clojure’s internal representation for data.
See the explanation in the text.
receiver:
In the message/method metaphor, the receiver is the particular instance
to which a method is applied.
recursion:
Traditionally, a book’s definition of recursion reads “See recursion.”
Because I’m a humorless git, I’ll point you to the appropriate section
of this book.
repl:
The read-eval-print loop. It reads a Clojure expression, evaluates it,
and prints the result. Also used to refer to Clojure’s interactive
interpreter. See the explanation in the text.
respond to a message:
An object responds to a message if it has method with that name.
rest arguments:
When a functions function’s parameter list contains an &, that signals
that all remaining arguments should be collected into a sequence and
associated with the parameter following the &. Those arguments are
referred to as the “rest arguments”.
send a message:
Sending a message is a stylized way of applying a function. The
dispatch function uses the message, an instance, and an argument list
to find the function. That function is applied to an argument list
composed of the original argument list and the instance.
seq:
Either a list or a lazyseq.
sequence:
An umbrella term referring to Clojure’s list, vector, and seq data types.
All sequences can be indexed by integers (starting with 0).
set:
A datatype in Clojure that acts much like a mathematical set. In
particular it’s easy to test membership in a set. A set is a callable. As
such, it returns true iff its single argument is in the set.
shadowing:
When symbols can be defined to refer to values and a language allows
such binding expressions to be nested, an enclosed definition shadows
an enclosing one using the same name. In that case, evaluation of the
symbol means the enclosed value.
In an object-oriented language, a method defined in a subclass
shadows a method with the same name in a superclass. In that case, the
dispatch function applied to an instance of the subclass will pick the
subclass version.
side effect:
A “pure” function takes inputs, calculates a result, and does nothing
else. A function with side effects can, during its calculation, change
state in a way observable from outside the caller. For example, it may
perform I/O. Or it may change the value of a global variable.
signature:
The name of a function (or method), together with its parameter list.
state:
Data that can be mutated, especially when the changes are made via
side effects.
structure sharing:
Languages that have only immutable data structures seem to require
much wasteful copying. In fact, both the new and old copies will share
most of their structure. This is analogous to video compression, where
frame N+1 is stored as only what changed to the frame N.
symbol:
A Clojure datatype that is typically used to refer to a value.
syntactic sugar:
Special syntax in a language to make common operations easier to
write. Often disparaged by purists. “Syntactic sugar causes cancer of
the semicolon.”—Alan J. Perlis.
unbound symbol:
A symbol in an expression that is to be evaluated to yield a value—but
no binding has been established for the symbol. (That is, it does not
appear in an enclosing let or function parameter list.)
value:
In this book, I use “value” to refer to any piece of Clojure data, be it an
integer, a list, a vector, or whatever.
vector:
A Clojure sequence that has the property that the last element is as fast
to access as the first. Vectors are callables.
zipper:
A data structure that simulates random movement through, and editing
of, immutable trees. They’re explained in a chapter.