Applying Compose Method
After I wrote that post on precedence, map, and function composition in Scala, I started to wonder: I’ve been thinking that I should experiment more with applying Compose Method. That refactoring recommends that, if I start with the original version of my code,
data.foreach(s => writer.addDocument(createDocument s))
then I should extract the body to a method. Which, I guess, would lead me to something like this?
data.foreach(addStringAsDocument(_, writer))
Except that that’s actually not what Compose Method really recommends: that’s merely one standard way of applying it to languages that are somewhat lacking in expressive possibilities. If all you have are manual looping constructs, and if you want to “keep all of the operations in a method at the same level of abstraction” (Smalltalk Best Practice Patterns, p. 22), then yeah, you’ll pull out your loop bodies to methods, but there are other ways to reach that end.
So, looking at the code that I actually ended up with that post (with the kind help of my readers),
data.map(createDocument).foreach(writer.addDocument)
is everything there at the same level of abstraction? That’s not entirely clear to me: if I wanted to, I could certainly extract a couple of methods out of that, and end up with something like this:
addDocumentsToWriter(createDocuments(data), writer)
Examining Alternatives
We’ve seen four examples of how that code could look; let’s replace the first of those with one that raichoo suggested on the previous post, giving us the following list:
data.foreach(createDocument _ andThen writer.addDocument)
data.foreach(addStringAsDocument(_, writer))
data.map(createDocument).foreach(writer.addDocument)
addDocumentsToWriter(createDocuments(data), writer)
Anybody want to argue for any of these being noticeably better or worse than all of the others? I’ll have to say: while they all seem fine to me, I can’t get too worked up over the need for using Compose Method in this case. Though, as I’ve been typing them up, I’ve wanted to add “fromString” in various places, which suggests that the method name createDocument
is perhaps not as well chosen as it could be: maybe I should have called it something like stringToDocument
instead?
Hard to say, I’m still happy enough with the third option. It says fairly directly that I’m starting with a bunch of data, turning it into a bunch of documents, and adding them to the writer: fine by me. (The first option seems approximately similarly expressive to me, as well.) There are, of course, situations where one or the other composed method would be preferable (as I said at the end of that earlier post, I ran into one an hour after I ran into the above!), but this doesn’t seem like one.
Recomposing and Natural Transformations
Maybe it’s the category theorist in me, but this also raises one other question: consider the two composed methods, 2 and 4 in the above list. Say that you reflexively went with option 2, but then decided that it didn’t seem quite right. You could (probably would) inline the function, and then distribute and regroup to end up with the fourth version; that seems like a pretty standard sort of thing to want to do. (It wouldn’t be too much of an abuse of language to call it a natural transformation!)
So: if we’re going to make a taxonomy of micro refactorings, might we not only also want to list ways of composing them (as, indeed, Refactoring itself suggests; see also Refactoring to Patterns or the hierarchies of patterns in A Pattern Language), but also ways of undoing them and composing them differently, along the lines of associativity laws?
Post Revisions:
This post has not been revised since publication.