What is the functional programming equivalent of the decorator design pattern?
For example, how would you write this particular example in a functional style?
Ok, first of all lets try to find all the main components of decorator pattern in respect to OOP. This pattern is basically used to decorate i.e add additional features to an existing object. This is the simplest possible definition of this pattern. Now if we try to find the same components that are there in this definition in the world of FP, we can say that additional features = new functions and object are not there in FP, rather FP has what you call data or data structure in various forms. So in FP terms this patterns becomes, adding additional functions for FP data structures or enhancing existing function with some additional features.
In Haskell, this OO pattern translates pretty much directly, you only need a dictionary. Note that a direct translation is not actually a good idea. Trying to force a OO concept into Haskell is kind of backwords, but you asked for it so here it is.
The Window Interface
Haskell has classes, which has all the functionality of an Interface and then some. So we will use the following Haskell class:
class Window w where
draw :: w -> IO ()
description :: w -> String
The Abstract WindowDecorator class
This one is a bit more tricky since Haskell has no concept of inheritance. Usually we would not provide this type at all and let the decorators implement Window
directly, but lets follow the example completely. In this example, a WindowDecorator
is a window with a constructor taking a window, lets augment this with a function giving the decorated window.
class WindowDecorator w where
decorate :: (Window a) => a -> w a
unDecorate :: (Window a) => w a -> a
drawDecorated :: w a -> IO ()
drawDecorated = draw . unDecorate
decoratedDescription :: w a -> String
decoratedDescription = description . unDecorate
instance (WindowDecorator w) => Window w where
draw = drawDecorated
description = decoratedDescription
Note that we provide a default implementation of Window
, it can be replaced, and all instances of WindowDecorator
will be a Window
.
The decorators
Making decorators can then be done as follows:
data VerticalScrollWindow w = VerticalScrollWindow w
instance WindowDecorator VerticalScrollWindow where
decorate = VerticalScrollWindow
unDecorate (VerticalScrollWindow w ) = w
drawDecorated (VerticalScrollWindow w ) = verticalScrollDraw >> draw w
data HorizontalScrollWindow w = HorizontalScrollWindow w
instance WindowDecorator HorizontalScrollWindow where
decorate = HorizontalScrollWindow
unDecorate (HorizontalScrollWindow w .. ) = w
drawDecorated (HorizontalScrollWindow w ..) = horizontalScrollDraw >> draw w
Finishing Up
Finally we can define some windows:
data SimpleWindow = SimpleWindow ...
instance Window SimpleWindow where
draw = simpleDraw
description = simpleDescription
makeSimpleWindow :: SimpleWindow
makeSimpleWindow = ...
makeSimpleVertical = VerticalScrollWindow . makeSimpleWindow
makeSimpleHorizontal = HorizontalScrollWindow . makeSimpleWindow
makeSimpleBoth = VerticalScrollWindow . HorizontalScrollWindow . makeSimpleWindow
You can "decorate" functions by wrapping them inside other functions, typically using some form of higher order function to perform the wrapping.
Simple example in Clojure:
; define a collection with some missing (nil) values
(def nums [1 2 3 4 nil 6 7 nil 9])
; helper higher order function to "wrap" an existing function with an alternative implementation to be used when a certain predicate matches the value
(defn wrap-alternate-handler [pred alternate-f f]
(fn [x]
(if (pred x)
(alternate-f x)
(f x))))
; create a "decorated" increment function that handles nils differently
(def wrapped-inc
(wrap-alternate-handler nil? (constantly "Nil found!") inc))
(map wrapped-inc nums)
=> (2 3 4 5 "Nil found!" 7 8 "Nil found!" 10)
This technique is used extensively in functional libraries. A good example is wrapping web request handlers using Ring middleware - the linked example wraps parameter handling for html request around any existing handler.
The Joy of Clojure talks about this very issue in chapter 13.3, "A lack of design patterns". According to the JoC, the -> and ->> macros are somewhat analogous to the decorator pattern.
I'm not 100% sure but I think the C9 lecture series on advanced functional programming explains the problem really good.
Aside from this you can use just the same technique inside F# (it supports just the same OO mechanism) and in this special case I would do so.
I guess it's a matter of tast and the problem you are trying to solve.
Something like this:
class Window w where
draw :: w -> IO ()
description :: w -> String
data VerticalScrollingWindow w = VerticalScrollingWindow w
instance Window w => Window (VerticalScrollingWindow w) where
draw (VerticalScrollingWindow w)
= draw w >> drawVerticalScrollBar w -- `drawVerticalScrollBar` defined elsewhere
description (VerticalScrollingWindow w)
= description w ++ ", including vertical scrollbars"