Monday, February 19, 2007

Design Patterns Aren't, Revisited

If you liked Mark Jason Dominus' take on Design Patterns, you'll probably get a kick out of Tony Morris' take on refactoring:
Ask your average Java (or C#) programmer to ‘write a method that takes a list of integers, adds 10 to each element, converts the result to a String, prepends *** to it and returns the resulting list’.
Tony takes this trivial problem and modifies it a few times to show how a simple and obvious implementation in Java (or the moral equivalent) slowly gets refactored into something so complex, it's hard to see the problem.

The punchline? The solution is dead trivial with higher order functions:
addTenAndConvert = map (("***" ++) . show . (+10))
Note that both the problem and solution here are contrived to highlight Haskell at its best. But the deeper point remains -- refactorings are patterns, and patterns (as commonly used) exist to describe recurring problems due to a language's design. As Mark points out (just like Peter Norvig pointed out before him), problems that merit a "pattern" in one language may not be problems in another language, or may be so trivial as not to merit a name in another language.

Although Mark and Peter Norvig were talking about design patterns, the critique certainly applies to many refactorings, which are really just (re-)implementation patterns.

8 comments:

Anonymous said...

In C#, assuming you have a List<int>:

list.ForEach(delegate(int i) { Console.WriteLine("***" + (i + 10)); });

And in C# 3.0 you can even get rid of the delegate.

keithb said...

The more of these articles I read banging on "Patterns" and now "Refactoring" the more bemused I become.

What am I to believe, that the Haskell community is so protean that all Haskell programmers only ever solve new, unique problems in new, unique ways and so the very concept of a "recurring solution to a recurring problem" is one they have no need of?

It's nice for the Haskell community that the language has a lot of the GoF patterns built in--I'd be interested to see the ways in which the hundreds of other patterns that have been written up since GoF vanish away in Haskell, too.

Presumably, I'm also supposed to believe that all Haskell programmers emit code from their fingers of such perfection that it never needs to be revisited under any circumstances? And so the Haskell programmer may also dispence with the concept of behviour-preserving transformations done with the intention of "improving the design of existing code".

And presumably, it could never be that a Haskell programmer found themselves restructuring their code to move it towards a solution that they'd seen in someone else's code. That would be silly.

Here's a thought, though: the standard prelude has a bunch of code in it that, it seems, gets used again and again. It couldn't be, could it, that there are sort of "patterns" in the way that Haskell programmers solve problems? Solutions that have cropped up again and again in different Haskell programs and have been kind of "factored out" into a collection of recurring solutions. Nah, that can't be right, because collections of recurring solutions like that are nothing but a sign of weakness in the language.

emk said...

The point of refactoring, really, is that each refactoring has a dual: For every "Extract Method", there's an "Inline Method". For every "Extract Superclass", there's a "Flatten Hierarchy". The Refactoring book is quite explicit about this.

In theory, you're supposed to choose the refactoring that makes your program simpler. Have a superclass with only one subclass? Flatten it. Need to create something similar to an existing class? Extract a superclass that's shared between both.

In Haskell, you just have different refactorings: "Extract Typeclass", "Convert function to return values in monad", "Extract freaky combinator library", "Convert to point-free style" and so on.

lionel Barret said...

@keith
you completely missed the point.
the very concept of a "recurring solution to a recurring problem" is useful for any language.

but, the GOF patterns are deeply linked to java/C++. the author is simply underlining the fact that GOF patterns are less needed is haskell. It is also true for Python, Ruby, Lisp (Norvig did a presentation that show it) and Smalltalk.

Others, more high-level patterns will be needed for haskell use. But they won't be the GOF patterns. Down the road, they may even stimulate the creation of another, better language.

GOF patterns as a set of solutions are not really relevant anymore (except in Java/C++*). But the process that creates them (abstract recurring problem into some high quality code) is still valuable and many haskellers do use it.

* From an innovation-centric point of view this two language are not relevant anymore. They ARE only relevant from a business point of view.

keithb said...

Actually, Lionel, I do get the point. What I don't get is how as nugatory a statement as "the GOF patterns are deeply linked to java/C++. [and so] are less needed is haskell." has spawned this cottage industry of articles showing off just how true that is (with varying degrees of glee and condecesion), and working their way up on from there to condemn the whole patterns movement as dangerous. For instance: "If the Design Patterns movement had been popular in the 1980's, we wouldn't even have C++ or Java; we would still be implementing Object-Oriented Classes in C with structs, and the argument would go that since programmers were forced to use C anyway, we should at least help them as much as possible."

So, idenitifying patterns holds back progress in language design? Please.

lionel barret said...

@keithb
Ah, your first comment wasn't clear (at least for me). On the whole, I agree with you.

I think the articles you cite confuse the fact that "finding and cataloging common patterns is damn useful" and the fact that "the specific GOF solutions proposed in the book are sort of obsolete now".

It's not 'no patterns anymore!!' but 'we need to build new patterns!'. I'll stop there, I am repeating myself. :P

Jonathan said...

What bothers me about the article, and the one before it, is that the author never shows us the Haskell code for all the variants that the java programmer is expected to offer.

For example, the function static List~String~ addTenAndConvert(List~Integer~ list, String s, boolean prepend)

As far as I know, this isn't a one liner in Haskell if you actually want the ability to set or clear the prepend flag at runtime.

keithb said...

Hi Lionel,
The funny thing is, the folks that write these anti-patterns articles aren't stupid, so much so that I begin to wonder if the confusion you correctly (I believe) identify isn't at the very least disingenuous, if not outright dishonest.

GoF isn't great, but it's also more than a decade old and the patterns community has moved on.

Then there's the "design patterns aren't what Alexander meant" thread: when I workshopped some of my patterns at EuroPLoP one reviewer commented that they showed signs that I'd "read a lot of Alexander" and this was not viewed as a good thing. The community knows that GoF has been abused, and they know that what they do has diverged from the original Alexandrian vision. But they continue to develop and grow what they do. If the critics made some coherent comment on the patterns movement as it stands today, in 2007, they might seem a little less as if, well, as if they have either something to hide or something to be afraid of.