I have observed pattern of design emerging in various Haskell libraries, for example vty-ui, GlomeTrace, and XMonad. Here is an excerpt from vty-ui’s interface. The snippet is long but simple, and I need all of it to demonstrate my point.

class Widget w where render :: DisplayRegion -> w -> Image growHorizontal :: w -> Bool growVertical :: w -> Bool primaryAttribute :: w -> Attr withAttribute :: w -> Attr -> w mkImage :: Widget a => Vty -> a -> IO Image data AnyWidget = forall w. Widget w => AnyWidget w data Text data Box data Fill instance Widget AnyWidget instance Widget Text instance Widget Box instance Widget Fill text :: Attr -> String -> Text hBox :: (Widget a, Widget b) => a -> b -> Box vBox :: (Widget a, Widget b) => a -> b -> Box hFill :: Attr -> Char -> Int -> Fill vFill :: Attr -> Char -> Fill

This module models the concept of a GUI widget. It defines a class with a basis of operations on Widgets, and then instantiates a few data types into the class. To facilitate passing around arbitrary widgets instead of specific instances, it provides an AnyWidget class. This Any* class is the tell-tale sign of this antipattern.

When I was using the above interface, I found myself immediately wrapping the results of text, hbox, and the other combinators in AnyWidget. By throwing away the information of exactly what instance of Widget it was I lost nothing, all the same operations were available to me. Astute readers might notice that withAttribute is an exception, because it has a w to the left and to the right of an arrow, so if I pass it a Box, I know I will get a Box back. But I cannot use that knowledge for anything else, so it doesn’t buy me anything.

There is a simpler and more direct way to encode the same thing. Here is the interface I would have written to this library:

data Widget = Widget { render :: DisplayRegion -> Image, growHorizontal :: Bool, growVertical :: Bool, primaryAttribute :: Attr, withAttribute :: Attr -> Widget } mkImage :: Vty -> Widget -> IO Image text :: Attr -> String -> Widget hBox :: Widget -> Widget -> Widget vBox :: Widget -> Widget -> Box hFill :: Attr -> Char -> Int -> Widget vFill :: Attr -> Char -> Widget

I know just from looking at the types that all the code in the module could be converted to this style. Every operation has the exact same assumptions as in the old version, and all the extra guarantees that the old one provided were useless because they were never used as assumptions. Plus, you don’t have to wrap every call to a combinator with AnyWidget. By exposing the constructor for the Widget type, it gives users the same opportunity to make custom Widgets as the class interface gave.

You might be thinking, “but what if somebody else made a widget type which did use the additional assumptions that the separated data types provided, for example,

data Window instance Widget Window window :: Widget w => (Int,Int) -> w -> Window windowSize :: Window -> (Int,Int) adjustSize :: (Int,Int) -> Window -> Window

“Haven’t we just excluded this possibility from the interface?” Turns out that we have not; just replace the instance with a function:

toWidget :: Window -> Widget

See how this is going? Classes become data types, instances become functions. I’m just manually “expanding” what GHC does for you. Of course, the expansion is smaller than the original.

I advocate a direct programming style in Haskell. Advanced type system features have their place, but plain old functions go a long, long way. Functions are the masters of reuse: when you use an advanced feature, you need a yet more advanced feature to abstract over it (think: classes < existential types < universally quantified constraints < unknown). But all you need to abstract over a function is another function.