Subtype polymorphism in Haskell

Having actual widget objects is something that's very object-oriented. A commonly used technique in the functional world is to instead use Functional Reactive Programming (FRP). I'll briefly outline what a widget library in pure Haskell would look like when using FRP.


tl/dr: You don't handle "Widget objects", you handle collections of "event streams" instead, and don't care from which widgets or where those streams come from.


In FRP, there's the basic notion of an Event a, which can be seen as an infinite list [(Time, a)]. So, if you want to model a counter that counts up, you'd write it as [(00:01, 1), (00:02, 4), (00.03, 7), ...], which associates a specific counter value with a given time. If you want to model a button that is being pressed, you produce a [(00:01, ButtonPressed), (00:02, ButtonReleased), ...]

There's also commonly something called a Signal a, which is like an Event a, except that the modeled value is continuous. You don't have a discrete set of values at specific times, but you can ask the Signal for its value at, say, 00:02:231 and it will give you the value 4.754 or something. Think of a signal as an analogue signal like the one on a heart charge meter (electrocardiographic device/Holter monitor) at a hospital: it's a continuous line that jumps up and down but never makes a "gap". A window does always have a title, for example (but perhaps it's the empty string), so you can always ask it for its value.


In a GUI library, on a low level, there'd be a mouseMovement :: Event (Int, Int) and mouseAction :: Event (MouseButton, MouseAction) or something. The mouseMovement is the actual USB/PS2 mouse output, so you only get position differences as events (e.g. when the user moves the mouse up, you'd get the event (12:35:235, (0, -5)). You'd then be able to "integrate" or rather "accumulate" the movement events to get a mousePosition :: Signal (Int, Int) that gave you absolute mouse coordinates. mousePosition could also take into consideration absolute pointing devices such as touch screens, or OS events that reposition the mouse cursor, etc.

Similarly for a keyboard, there'd be a keyboardAction :: Event (Key, Action), and one could also "integrate" that event stream into a keyboardState :: Signal (Key -> KeyState) that lets you read a key's state at any point in time.


Things get more complicated when you want to draw stuff onto the screen and interact with widgets.

To create just a single window, one would have a "magic function" called:

window :: Event DrawCommand -> Signal WindowIcon -> Signal WindowTitle -> ...
       -> FRP (Event (Int, Int) {- mouse events -},
               Event (Key, Action) {- key events -},
               ...)

The function would be magical because it would have to call the OS-specific functions and create a window (unless the OS itself is FRP, but I doubt that). That is also why it is in the FRP monad, because it would call createWindow and setTitle and registerKeyCallback etc in the IO monad behind the scenes.

One could of course group all of those values into data structures so that there would be:

window :: WindowProperties -> ReactiveWidget
       -> FRP (ReactiveWindow, ReactiveWidget)

The WindowProperties are signals and events that determine the look and behavior of the window (e.g. if there should be close buttons, what the title should be, etc.).

The ReactiveWidget represents S&Es that are keyboard and mouse events, in case you want to emulate mouse clicks from within your application, and an Event DrawCommand that represents a stream of things you want to draw on the window. This data structure is common to all widgets.

The ReactiveWindow represents events like the window being minimized etc, and the output ReactiveWidget represents mouse and keyboard events coming from the outside/the user.

Then one would create an actual widget, let's say a push button. It would have the signature:

button :: ButtonProperties -> ReactiveWidget -> (ReactiveButton, ReactiveWidget)

The ButtonProperties would determine the color/text/etc of the button, and the ReactiveButton would contain e.g. an Event ButtonAction and Signal ButtonState to read the button's state.

Note that the button function is a pure function, since it only depends on pure FRP values like events and signals.

If one wants to group widgets (e.g. stack them horizontally), one would have to create e.g. a:

horizontalLayout :: HLayoutProperties -> ReactiveWidget
                 -> (ReactiveLayout, ReactiveWidget)

The HLayoutProperties would contain information about border sizes and the ReactiveWidgets for the contained widgets. The ReactiveLayout would then contain a [ReactiveWidget] with one element for each child widget.

What the layout would do is that it would have an internal Signal [Int] that determined the height of each widget in the layout. It would then receive all of the events from the input ReactiveWidget, then based upon the partition layout select an output ReactiveWidget to send the event to, meanwhile also transforming the origin of e.g. mouse events by the partition offset.


To demonstrate how this API would work, consider this program:

main = runFRP $ do rec -- Recursive do, lets us use winInp lazily before it is defined

  -- Create window:
  (win, winOut) <- window winProps winInp

      -- Create some arbitrary layout with our 2 widgets:
  let (lay, layOut) = layout (def { widgets = [butOut, labOut] }) layInp
      -- Create a button:
      (but, butOut) = button butProps butInp
      -- Create a label:
      (lab, labOut) = label labProps labInp
      -- Connect the layout input to the window output
      layInp = winOut
      -- Connect the layout output to the window input
      winInp = layOut
      -- Get the spliced input from the layout
      [butInp, layInp] = layoutWidgets lay
      -- "pure" is of course from Applicative Functors and indicates a constant Signal
      winProps = def { title = pure "Hello, World!", size = pure (800, 600) }
      butProps = def { title = pure "Click me!" }
      labProps = def { text = reactiveIf
                              (buttonPressed but)
                              (pure "Button pressed") (pure "Button not pressed") }
  return ()

(def is from Data.Default in data-default)

This creates an event graph, like so:

     Input events ->            Input events ->
win ---------------------- lay ---------------------- but \
     <- Draw commands etc.  \   <- Draw commands etc.      | | Button press ev.
                             \  Input events ->            | V
                              \---------------------- lab /
                                <- Draw commands etc.

Note that there doesn't have to be any "widget objects" anywhere. A layout is simply a function that transforms input and output events according to a partitioning system, so you could use the event streams you gain access to for widgets, or you could let another sub-system generate the streams entirely. The same goes for buttons and labels: they are simply functions that convert click events into draw commands, or similar things. It's a representation of complete decoupling, and very flexible in its nature.


To understand what OOP, such as subtype polymorphism, can be done in Haskell you can look at OOHaskell. This reproduces the semantics of a variety of powerful OOP type systems, keeping most type inference. The actual data encoding was not being optimized, but I suspect type families might allow better presentations.

Modelling the interface hierarchy (e.g. Widget) can be done with type classes. Adding new instances is possible and so the set of concrete widgets is open. If you want a specific list of possible widgets then GADTs can be a succinct solution.

The special operation with subclasses is upcasting and downcasting.

This is first needed to have a collection of Widgets, and usual result is to use existential types. There are other interesting solutions if you read all the bits of the HList library. The upcasting is fairly easy and the compiler can be certain that all casts are valid at compilation time. The downcasting is inherently dynamic and requires some run-time type information support, usually Data.Typeable. Given something like Typeable the downcasting is just another type class, with the result wrapped in Maybe to indicate failure.

There is boilerplate associated with most of this, but QuasiQuoting and Templating can reduce this. The type inference can still largely work.

I have not explored the new Constraint kinds and types, but they may augment the existential solution to upcasting and downcasting.


The wxHaskell GUI library makes excellent use of phantom types to model a widget hierarchy.

The idea is the following: all widgets share the same implementation, namely they are foreign pointers to C++ objects. However, this doesn't mean that all widgets need to have the same type. Instead, we can build a hierarchy like this:

type Object a = ForeignPtr a

data CWindow a
data CControl a
data CButton a

type Window  a = Object  (CWindow a)
type Control a = Window  (CControl a)
type Button  a = Control (CButton a)

This way, a value of the type Control A also matches the type Window b, so you can use controls as windows, but not the other way round. As you can see, subtyping is implemented via a nested type parameter.

For more on this technique, see section 5 in Dan Leijen's paper on wxHaskell.


Note that this technique appears to be limited to the case where the actual representation of widgets is uniform, i.e. always the same. However, I am confident that with some thought, it can be extended to the case where widgets have different representations.

In particular, the observation is that object-orientation can be modeled by including the methods in the data type, like this

data CWindow a = CWindow
    { close   :: IO ()
    , ...
    }
data CButton a = CButton
    { onClick :: (Mouse -> IO ()) -> IO ()
    , ...
    }

Subtyping may save some boilerplate here, but it's not required.