The beginning of a series wherein we show how to implement selected design patterns from Design Patterns: Elements of Reusable Object-Oriented Software. This week, we take a look at the venerable Abstract Factory pattern.
A common question new Haskell programmers have is, “How do I implement <this-or-that> OOP design pattern in Haskell.” These curious and resolute dames and gents are (quite reasonably) hoping to leverage their background in object-oriented programming towards their new goal of learning functional programming and Haskell. Unfortunately, it’s not always straightforward to translate patterns that rely on the familiar notions of class hierarchies, inheritance, instantiation, virtual methods, collaborators, encapsulation, and mutation to the alien landscape of datatypes, typeclasses, parametricity, immutability, pattern matching, and higher-order functions that is Haskell. Worse, many a young and impressionable Haskellers develop misleading sets of analogies between OOP concepts and Haskell features, ultimately leading to confusion and frustration down the line. For example, Haskell typeclasses bear only the most superficial resemblance to Java interfaces.
This series has two goals: (1) to provide new Haskell programmers with a menagerie of translations between familiar OOP code idioms and Haskell, and (2) to dispel dubious analogies between OOP languages features and Haskell language features by replacing them with well-founded and practical analogies. As the book of design patterns is often affectionately (and sometimes derisively) referred to as Gang of Four, we’ll unimaginatively call this series Haskell GoF.
Expect new posts once every blue moon.
Identifying the Pattern
We should begin our explorations with a shared understanding of the topic at hand. In particular, what makes something a design pattern? The concept of a design pattern became popular in programming circles due to the broadly influential book on (brick and mortar) architectural design, The Timeless Way of Building by Christopher Alexander, architect and emeritus professor at the University of California, Berkeley.
Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice. – Christopher Alexander
To paraphrase Dr. Alexander, a design pattern is an outline of a solution to a common problem that does not admit a generic solution. In programming, we often come across a problem that does admit its own generic solution. Since the solution to such a problem is generic, we need only write the solution down once, package that solution into a library, and import the library wherever the need arises. Such solutions are not design patterns. A design pattern for a recurring programming problem is a solution template that resists codification.
Next, we need to understand why certain solutions (or outlines of solutions) can’t be codified. One reason is that sometimes the problem itself is vague. Often, we can spot similarities in various coding problems—similarities that lead to similar but somewhat different solutions—without being able to identify a precise defining characteristic of such a class of problems. Thus, while a vague problem usually can’t have a precise solution, it can have a vague solution. This vague solution becomes a design pattern.
Another reason a solution might not be realizable as a library is that we might be missing the right language features. There’s lots of talk about the relative abstraction power of various programming languages. Really, this so-called power of abstraction is simply the ability to parametrize repeated syntactical forms by the sub-expressions by which those forms vary, enabling elimination of the syntactic repetition. (What else do you think a function is?) Through their features and syntactic rules, different languages enable and prohibit abstraction of various syntactical forms.1 The repeated syntactic forms that can’t be abstracted become design patterns instead of libraries.
Before proceeding, there’s one more point of terminology on which we should agree, since it shows up so frequently in discussions of patterns: the usage of the word abstract. Programmers tend to attach a peculiar meaning to the word abstract. They often mean “complicated implementation hidden behind a simple API,” but this is not a description of something abstract. On the contrary, this is a description of something very real and concrete; we have a full implementation inside there, after all. A human body is quite complex inside, but the (non-lethal) ways in and out are clearly defined and simple, so should we call human bodies abstract? On top of that, programmers already have another word for this concept of implementation hiding: encapsulation. Let’s not use the term abstract to describe a complex, concrete system with a small, tidy surface when we already have a much more evocative word to describe it. Abstract and concrete are at odds, anyway.
Consider: what’s abstract about abstract art? A central feature of abstract art is that it avoids depiction of identifiable, concrete objects. Instead, we have pure geometry of shapes, lines, brushstrokes, and color. Instead of an art piece having a definite meaning, different onlookers will be able to imagine different meanings. The piece is open to the interpretation of the onlooker, and this property cuts to the heart of what it means for something to be abstract. Indeed, abstract means “open to interpretation.”
Abstraction helps us decouple our code in ways that encapsulation can’t. On the left, we see the dependency structure we get when we encapsulate a third-party library. On the right, we abstract it. When we abstract it, fewer of our code units depend on the third-party library, decoupling our application code from our external dependencies.
Now that we agree (at least for the purposes of this discussion) on what our words mean—on what design patterns are and why we might need them and on the nature of abstraction more generally—we’re ready to tackle our first pattern, the Abstract Factory.
Provide an interface for creating families of related or dependent objects without specifying their concrete classes. – GoF
Suppose we’d like to write an algorithm or build up a data structure using a bespoke set of primitives while allowing the interpretation of that algorithm or data structure into several different concrete forms. The Abstract Factory Pattern gives us a way to accomplish this.
That’s a mouthful, so let’s consider a practical example: graphical user interfaces (GUIs). Each software platform (Linux, Mac, Web, Windows, etc.) provides its own toolkit for rendering things to the screen. These toolkits don’t necessarily have any commonalities between them, so if we want to write a cross-platform program, we’re left to repeatedly implement our GUI for each platform we target. Alternatively, we can come up with our own set of primitives for describing our program’s GUI. We can write more complicated components in terms of our primitives, and in turn combine those components to create our complete GUI, abstractly (that is, without reference to any of the concrete, platform-dependent toolkits). Then, instead of implementing every high-level component in each toolkit, we need merely define how each primitive (and each combining rule) is implemented in each of the various toolkits. We thus interpret the exact same abstract, platform-independent GUI code into concrete GUIs appropriate to each target platform. Qapla’!
Let’s look at an implementation in classic Java.2
... indicates details that we’ve omitted for brevity.)
LatexWidget wraps3 a third-party toolkit for one of our target platforms. Notice that they have very different APIs. Nevertheless, the differences in their APIs won’t stop us from finding a mutual abstraction: we will still be able to make an implementation of
AbstractWidgetFactory for both of them, despite their idiosyncrasies.
Before moving on, it’s important to understand that
AbstractWidgetFactory is not an (abstract widget) factory. Rather, it is an abstract (widget factory). In other words, it is not a concrete factory that produces hypothetical widgets; it hypothesizes a factory that produces real widgets. The factory itself is abstract, not the widgets.
AbstractWidgetFactory is abstract in the sense that its methods are not implemented and must be given an interpretation by any implementing classes.4
Using the primitives provided by
AbstractWidgetFactory, we can build up larger reusable components, and ultimately combine those components to build out our entire GUI. This can be accomplished without any knowledge of (e.g. without depending on) the various concrete subtypes of
Widget. As such, we have a component that works with any subtype of
Widget—even ones that haven’t been conceived of yet—so long as we’re given a factory that will produce widgets of that type. In this way, we completely decouple the implementation of our GUI from the hardware toolkits our target platforms provide. Here’s an example of one reusable component, a
table is written in terms of the
AbstractWidgetFactory primitives, we will be able to use it generically with any of the platform toolkits by passing in different implementations of
AbstractWidgetFactory. “Write once run anywhere” never felt so visceral!
Finally, let’s see the implementation of
One glaring flaw in this design is that we have to perform unsafe type casts from
HtmlWidgets in order to implement
row. Nothing can stop us from accidentally passing
LatexWidgets into an
HtmlWidgetFactory, causing a runtime error that will crash our program. Indeed, the GoF authors are aware of and admit to this flaw. The flaw can be completely overcome with the use of type parameters, but lacking type parameters, the GoF authors suggest that this danger can be mitigated by ensuring that only one factory is ever in scope at a time and that all
Widgets in your program be created from that one factory.6
And that’s all there is to the Abstract Factory. We’ve accomplished our goal of writing our UI once against a set of abstract primitives (i.e. the methods of
AbstractWidgetFactory), affording us the ability to interpret that UI against each of our concrete toolkits by simply interpreting the primitives (i.e. by implementing a concrete subclass of
AbstractWidgetFactory). This is the crucial characteristic of the Abstract Factory pattern. Our operative code does not have a dependency on either
LatexWidget, nor does it have a dependency on their respective factories. Neither does
AbstractFactory have a dependency on either of
LatexWidget. This is important.
As a point of contrast, we can imagine a code structure where we encapsulate
LatexWidget (or both) behind an API that is more convenient for our immediate usage. This is a great practice, but it’s not an abstract factory.7 Sometimes you want such a code structure, but for the problem we’re considering today it’s inadequate; it doesn’t accomplish our goal of having platform-independent code. Our code would depend on the wrapper class, which in turn depends on the concrete, platform-dependent subclass(es) of
Widget that the wrapper encapsulates. This restricts us to only those toolkits that the author of the wrapper class thought to include. It also forces us to depend on all the included toolkits, when we’d likely only want to depend on one per any given executable.
On the other hand, the genuine abstraction characteristic of the Abstract Factory Pattern (versus the encapsulation provided by the Adapter Pattern) allows us to decouple our operative code from dependency on any concrete toolkit. Without abstraction, we cannot achieve this separation.
Haskell Abstract Factory
All in one go, I’ll show you my Haskell version of the above code. We’ll go into more detail below, so just give it a quick skim for now.
The first thing one might notice is the curious absence of anything called a factory. With that in mind, let’s take a closer look at the
Widget typeclass represents the notion that certain types can be said to be
Widget types.8 A type is a
Widget type if we have ways to create and combine values of that type the in the ways we expect to be able to create and combine widgets (namely, that appropriate functions
heading exist). That is to say, in this translation of the above Java code, it’s the typeclass
Widget that plays the role of the abstract factory, and it’s the typeclass instances
instance Widget Html and
instance Widget Latex that play the roles of the concrete factory implementations. The type parameter
a, oddly enough, plays the role of the Java version’s
The most important question is this: does the Haskell version solve the core problem that the Abstract Factory Pattern is meant to solve? Recall the core problem, as stated by GoF.
Provide an interface for creating families of related or dependent objects without specifying their concrete classes. – GoF
We see that, indeed, the
Widget typeclass provides us with a means of creating GUI widgets without specifying their concrete
classes types. The evidence lies in
aboutMe each creates a widget of an unspecified type
a. That is,
aboutMe can be used to create widgets of any type, even widget types we haven’t thought of yet: they’re polymorphic over all widget types. By writing polymorphic functions with a
Widget typeclass constraint, we’re able to completely decouple our GUI code from any concrete GUI toolkit. Furthermore, as long as we can write a
Widget typeclass instance to bootstrap the process, we’re able to translate our GUI code into any existing or future toolkit.
Alternate Haskell Versions
Lest you develop the mistaken notion that typeclasses correspond exactly to instances of the Abstract Factory Pattern, we’ll take a look at two other Haskell versions of the same program.
You might be thinking that my analogy between the
Widget typeclass and the Abstract Factory Pattern is somewhat thin. After all, where’s the actual factory? Consider an alternative, more literal Haskell translation of the Java version. Suppose that we had no
Widget typeclass, and instead had a
In this encoding, we would explicitly pass a value of type
WidgetFactory a to
Instead of invoking a top-level
rows to a list of widgets
childWidgets, you’d extract the
rows field from your factory and apply to the list of widgets, vis a vis
(rows factory) childWidgets. Instead of class instances, you’d write functions that return
Notice that in both the typeclass version and the record version, the type parameter
a guards us from passing the wrong kind of widget into a factory. In the Haskell versions, the types
Latex don’t need a common subtype the way they do in the Java version. In fact, the type parameter completely obviates the need for a type hierarchy, as the only reason we had to have a type hierarchy in the Java version was so that we could provide signatures for the
Another thing to notice is that the record version allows us to have multiple factories for the same type in scope. We could have two different values of type
WidgetFactory Html, and we could choose between them for whatever various reasons. Sometimes this is what you want, and it’s slightly annoying to pull off in the typeclass version, making the record encoding preferable in such cases. In typical cases, though, having multiple factories for the same type is decidedly not what we want. We usually want to ensure that there’s only one factory of a given type in scope. The typeclass version ensures that this is the case, while the record version leaves this as a backdoor for potential bugs.9
Algebraic Datatype Encoding
While the typeclass version and the record version are morally the same encoding (the record encoding follows the typeclass encoding in an entierly formulaic way10), we can yet model the Abstract Factory Pattern in an altogether different way by inverting the priority of the principle players. By that, I mean we change our notion of which concepts are primitive and which concepts are composite.
In this encoding, the type
Widget consists of abstract syntax trees for our notions of what a widget should be and how we should be able to manipulate and combine widgets. We write our GUI code in terms of the abstract syntax. We then write the interpreters
asLatex that interpret the abstract syntax in terms of the respective concrete toolkits.
Notice that, at the end of the day, we end up writing what’s basically all the same code in all three versions. The cases for
asHtml end up having nearly identical code as the methods of the typeclass instance
Widget Html and the fields of the record
htmlWidgetFactory :: WidgetFactory Html. None of these versions results in significantly less code than either of the others.
All three encodings have their strengths and weaknesses. For example, a nice thing about the datatype encoding is that
Widgets are now serializable. A nice thing about the record encoding is that you can have multiple factories in scope and select one or the other at runtime, if that’s a thing you want to do. You could even construct new factories at runtime. Some nice things about the typeclass encoding are that it’s very DRY—your
Widget functions are top-level, so you neither need to keep pulling them out of some record nor need to keep passing said record down the call stack—and it ensures that there’s only one factory in scope for any given type, if that’s a thing you want to ensure.
I wanted to show you three different encodings because I don’t want you to get the idea that typeclasses are necessarily some kind of manifestation of the Abstract Factory Pattern. Typeclasses are their own thing, and it just so happens that they provide a way to emulate the Abstract Factory Pattern. But so do records and so do algebraic datatypes, so there’s no special connection between abstract factories and typeclasses. I also don’t want you to think that typeclasses correspond to OOP interfaces (really, records are closer to OOP interfaces, but still not quite the same). In general, we shouldn’t try to directly map OOP concepts onto Haskell concepts, because to do so is a form of the XY Problem. Instead, we should always take a deep breath and go back to the root problem we’re trying to solve. We may find a simple answer.
Recall that the basic problem the Abstract Factory Pattern solves is fundamentally about decoupling. The three above Haskell versions demonstrate two simple ideas. First, we have the record and typeclass encodings, which illustrate the utility of type parameters and callbacks for decoupling code. The record (respectively, the typeclass instance) is really just a way of passing in a bunch of callbacks to a function, when you think about it. Second, the datatype encoding illustrates the utility of domain-specific languages (DSLs) and interpreters for decoupling code. Write your program in terms of a simple, declarative grammar with a few primitives and basic combination rules. Then, interpret your program by simply interpreting the primitives and basic rules, a form of induction/recursion.
Design patterns are templates of solutions that—for whatever reason—evade being codified into a reusable function, component, or library.
Abstractions are open to interpretation. Abstract stands in contrast to concrete.
Merely hiding concrete implementation details doesn’t make something abstract; rather, it encapsulates (which is fine, encapsulation is appropriate at times).
Abstraction decouples code. Encapsulation does not.
An Abstract Widget Factory is an abstract (widget factory), not an (abstract widget) factory. Explicitly, it’s the factory that’s abstract, not the widgets. An abstract factory is a placeholder for a concrete factory that will be given to your code at some future time.
An OOP-style abstract factory can be modeled in Haskell using classes, records, or an algebraic datatype. None is clearly better than the others.
You can use type parameters and callbacks to decouple code.
You can use domain-specific languages and interpreters to decouple code.
In general, try to avoid thinking about how to directly translate OOP solutions to Haskell. Instead, go back to the original problem.
Keep it simple and don’t overthink things. At the end of the day, it’s all just passing arguments to functions.
Notice that the incredible facility for abstraction in Lisp has less to do with having copious language features (as Lisp has hardly any) and more to do with the elegant simplicity of its syntax. ↩
Java, first released in 1995, lacked type parameters before its fifth edition in 2004. This lack of type parameters had a profound impact on the nature of the code people wrote in Java. You wouldn’t think so, but the lack of type parameters inevitably leads to the style of mutation-heavy,
void-method-driven APIs we see in so many Java libraries today. When we give examples in this series, we’ll stick to classic Java, that is, Java without type parameters. This fits in better with the style of programming in GoF (published in 1994), which also lacks type parameters. ↩
We have to wrap the third-party toolkits because
Widgetis our class, so they can’t extend it. Later on, we’ll see how to do this wrapping using the Adapter Pattern. ↩
Indeed, this was the case for all interfaces before Java’s eighth edition. Interfaces are a mechanism for abstraction. As such, the
AbstractWidgetFactoryis redundant. We already know it’s abstract, because it’s an interface. One can only presume that the GoF authors used this redundant naming convention as a pedantic device to drive home their points. Unfortunately, the obtusely-redundant naming convention became part of the Java culture, leading to such absurdities as “Since
AbstractWidgetFactory.” No wonder programmers are so confused about the meaning of the word abstract! ↩
I’m not going to implement
LatexWidgetin Java, because it’s a huge pain in the ass. I will implement it in Haskell, though. ↩
The GoF authors suggest that the programmer accomplish this careful control of scope using the Singleton pattern, which we will examine in a subsequent post. ↩
It is an example of the Adapter Patter, though, which we’ll examine in a subsequent post. ↩
This is where we get the term type class. A type class is a class (or set) of types that all satisfy some required conditions. Type class instances define precisely how a particular member of the type class satisfies the required conditions. ↩
It’s usually not that big of a deal, to be honest. ↩