Composable Value Editors

Graphical User Interfaces (GUIs) in haskell are frustrating. It’s not yet clear what is the cleanest model for fitting GUIs into functional programming. Currently there are two main approaches:

  • Various effort at applying Functional Reactive Programming (FRP) to GUIs. These are somewhat experimental, and tend to be proof of concepts implementing a small range of GUI features (several of these libraries are listed here).

  • The full blown toolkits which provide a comprehensive imperative binding to mainstream toolkits. The two key contenders here are gtk2hs and wxHaskell.

Whilst enticing, the FRP approach doesn’t currently look appropriate for building rich GUI applications. wxHaskell and gtk2hs at least provide the functionality required, but the low level imperative approach based in the IO monad is tedious to a fluent haskell developer. Here’s a code snippet:

b <- buttonNew
image <- imageNewFromStock stockAdd IconSizeSmallToolbar
containerAdd b image
set b [buttonRelief := ReliefNone]
on b buttonActivated {
     ... button activated action ...

It’s not hard to write this sort of code, but it is tedious, especially considering the amount that is required to build a whole application.

This post outlines my experiments to reduce the amount of imperative code required for GUIs, yet retaining compatibility with the imperative toolkits. Initially I’ve been focussed on "value editors" (VEs) aka "forms". These are GUI components to capture/edit values of ideally arbitrary complexity. I’ve two key goals, composability and abstraction.

Composability: I want to be able to compose my value editors effortlessly. Whilst the existing toolkits let you compose widgets using containers and glue code, it’s verbose indeed.

Abstraction: I’d like to define my VEs independently from the underlying toolkit. But I’m looking for something more than a thin layer over the existing toolkits. I want to define my VEs in terms of the structure of the values involved, and worry about the formatting and layout later, if at all.

If we take this abstraction far enough, it should be possible to reuse our structural VEs definitions beyond gtk2hs and wxWindows. For example, a JSON generator+parser pair can be considered a VE – in the sense that to edit a value, one can generate the json text, edit the text, and then parse to recover the new value. Of course, it’s likely to be a balancing act between abstraction and functionality – we’ll have to see how this pans out.

An Abstract UI

OK, enough preamble, here’s a GADT I’ve devised to capture VEs:

-- | A GADT describing abstracted, user interface components for manipulating
-- values of type a.
data VE a where
    -- | A String field
    Entry :: VE String

    -- | An enumeration. A list of label string are supplied,
    -- the VE value is the integer index of the selected label.
    EnumVE :: [String] -> VE Int

    -- | Annotate a VE with a text label
    Label :: String -> VE a -> VE a

    -- | A "product" VE that combines values from two other VEs
    AndVE :: (VE a) -> (VE b) -> VE (a,b)

    -- | A "sum" VE that captures the value from either of two other VEs
    OrVE  :: (VE a) -> (VE b) -> VE (Either a b)

    -- | A VE for manipulating  a list of values. The supplied function lets the
    -- the VE display the list items to the user (eg for selection).
    ListVE :: (a->String) -> VE a -> VE [a]

    -- | Convert a VE over a type a, to a VE over a type b, given
    -- the necessary mappings. Either String captures the potential
    -- failure in the mapping.
    MapVE :: (a -> Either String b) -> (b -> a) -> VE a -> VE b

    -- | Annotate a VE with a default value
    DefaultVE :: a -> VE a -> VE a

-- A typeclass to build VEs
class HasVE a where
  mkVE :: VE a

(.*.) = AndVE
(.+.) = OrVE
infixr 5 .*.
infixr 5 .+.

And here’s an example usage for a simple data type:

data Gender = Male | Female deriving (Show,Enum)

data Person = Person {
    st_name :: String,
    st_age :: Int,
    st_gender :: Gender
} deriving (Show)

instance HasVE Person
    mkVE = MapVE toStruct fromStruct
        (   Label "Name" nonEmptyString
        .*. Label "Age"   mkVE
        .*. Label "Gender"   mkVE
        toStruct (a,(b,c)) = Right (Person a b c)
        fromStruct (Person a b c) = (a,(b,c))

nonEmptyString :: VE String
nonEmptyString = ...

instance HasVE Int ...
instance HasVE String ...
instance HasVE Gender ...

This captures in some sense the abstract semantics for an editor of Person values. We need to capture:

  • a non-empty string for the name,
  • an integer for the age
  • a gender enumeration

and know how to pack/unpack these into a person value.


But what can we do with this? We need to turn this abstruct VE into a concrete UI. There’s a library function to do this for an arbitrary VE:

data GTKWidget a = GTKWidget {
    ui_widget :: Widget,
    ui_set :: a -> IO (),
    ui_get :: IO (ErrVal a),
    ui_reset :: IO ()

uiGTK  :: VE  a -> IO (GTKWidget a)

The uiGTK function turns our abstract VE a into GTK component for editing a value of type a. In addition to building the compound widget, it gives us functions to:

  • put a value into the widget
  • recover a value from the widget
  • restore the widget to a default value

A higher level function constructs a modal dialog to get a value of type a from the user.

data ModalDialog e a = ModalDialog {
    md_dialog :: Dialog,
    md_gw :: GTKWidget a,
    md_run :: IO (Maybe a)

modalDialogNew :: String -> VE a -> IO (ModalDialog a)

Hence running this:

dialog <- modalDialogNew "Example 2" (mkVE :: Person)
ma <- md_run dialog

Results in this:

Example 2

Example 2

The automatically generated dialog is simple, but quite functional:

  • invalid fields have a red background, dynamically updated with each keystroke
  • Fields have sensible defaults – often invalid to force entry from a user

More complex UIs are of course possible. As should be clear from the VE GADT above we support sum and product types, lists, etc, and can map these with arbitrary code. Hence we can construct GTK UIs for a very large range of haskell values. A slightly more complex example composes the previous VE:

data Team = Team {
    t_leader :: Person,
    t_followers :: [Person]
} deriving (Show)

instance HasVE Team ...

Resulting in:

Example 3

Example 3

Recursive types are supported, so its possible to build GTK VEs for expression trees, etc.

JSON Serialisation

As I alluded to previously, given VE a, we can automatically generate a JSON generator and parser for values of type a:

data VEJSON a = VEJSON {
        uj_tojson ::  a -> DA.Value,
        uj_fromjson :: DA.Value -> Maybe a

uiJSON :: VE ConstE a -> VEJSON a

Related Work

Well into working on these ideas, I was reminded of two somewhat similar haskell projects: Functional Forms and Tangible Values. Functional Forms aims to ease the creation of wxHaskell dialogs to edit values. The exact purpose Tangeable Values is a little unclear to me, but it appears to be about automatically generating UIs suitable for visualising function behaviour and exploring functional programming.

Future Work

Currently I have a library that implements the VE GADT to automatically build GTK editors and JSON serialisers. There’s many ways to progress this work. Some of my ideas follow…

Whilst the generated GTK editor is a sensible default, there are only very limited ways in which the editor can be customised. I envisage a model where the uiGTK function takes an extra parameter akin to a style sheet, given extra information controlling the UI layout and formatting, etc.

I can envisage many other useful things that could automatically be derived from VE definitions:

  • equivalent functionality for wxHaskell
  • console GUIs
  • Funky UIs implemented with primitives more interesting than the standard toolkit widgets: eg zoomable UIs, or UIs more suited to table based platforms.
  • web GUIs. This could be done by automatically generating javascript and corresponding server side haskell code.

Finally, It might be worth investigate whether the GHC Generic mechansism might be used to automatically generate VE definitions.

So there’s plenty of directions this work can go, but right now I want to put it to the test and build an application!


15 thoughts on “Composable Value Editors

  1. Hi, I’m the author of the reactive-banana FRP library. One of its selling points is that it fits on top of any GUI framework (with some glue code), and you can always fall back to an imperative style if you feel the need to do so.

    Have a look at my collection of example GUI programs. It appears to me that reactive-banana is powerful enough to describe user interfaces that are much richer than the fairly rigid value editors.

    • Thanks. I will take a look at the reactive-banana library – I didn’t realise that it was ready for prime time.

      Whilst I agree that value editors are somewhat rigid, this can be desirable. I have previously built a (proprietary) application in python that has a richly interactive core, but is supported by dozens of value editors. Having a framework to make these VEs consistent and trivial to implement can provide much value.

      Whilst I’ve read a fair number of papers over the years, I have to concede that my knowledge of FRP is limited. How would a Value Editor in FRP be represented? In reactive-banana, what would be the equivalent to the GtkWidget definition?

      Perhaps value editors could be automatically generated from VE definitions in the reactive-banana framework also?

      • The simplest way to make a “value inputter” is to create the various widgets and then obtain their values as a Behavior. To display the combined values, you can create another set of widgets and use the sink function. This is exemplified in the Arithmetic.hs example.

        It’s also possible to have widgets both input and output values, but then you’re quickly in the realm of rich user interfaces. The CurrencyConverter.hs and the CRUD.hs examples demonstrate what is possible.

        In other words, there is no data type in reactive-banana that directly corresponds to a value editor. Instead, the goal is to make GUI programs easier in general.

        Of course, you can generate value editors in reactive-banana from their corresponding VE descriptions. You can even generate them in the underlying GUI framework and then put a little FRP glue code on top.

  2. I might be missing some GTK-specific trick here, but as far as I see your GTKWidget type doesn’t expose a way of being notified when the value it holds is changed by the user.

    • Sorry for the _very_ slow reply. I missed the moderation request for this comment.

      Yes – you are correct. The “form like” model assumes a dialog like behavior: The controlling code calls ui_set to put a value into a form, displays it to the user, and then finally calls ui_get to extract the resulting value.

  3. It appears to me that the type class HasVE is not very useful. In fact, I think it falls into the typeclass antipattern.

    Instead of specializing a polymorphic value (mkVE :: Person), you can just use a special value personVE right away. You don’t save any keystrokes by specifying one value editor for each type as a default.

    • The antipattern Luke Palmer describes is where a typeclass is used when an record of functions would be more appropriate. I don’t think that’s really relevant here.

      You are right that mkVE is a shorthand for {typeName}VE. But I like the consistency. eg instead of writing this:

          mkVE = MapVE toStruct fromStruct
              (   Label "Name" stringVE
              .*. Label "Age"   intVE
              .*. Label "Gender"   genderVE

      I can write this:

          mkVE = MapVE toStruct fromStruct
              (   Label "Name" mkVE
              .*. Label "Age"   mkVE
              .*. Label "Gender"   mkVE

      But also, in some circumstances the polymophism is useful. We can automatically build VEs for Maybe a if we can build one for a:

      instance (HasVE a) => HasVE (Maybe a)
          mkVE = MapVE  toMaybe fromMaybe (mkVE .+. VoidVE)
              toMaybe (Left a) = eVal (Just a)
              toMaybe (Right ()) = eVal Nothing
              fromMaybe (Just a) = Left a
              fromMaybe Nothing = Right ()
      • Granted on the record thing.

        Note that you don’t need a type class to have polymorphism, you can always use an explicit combinator
        maybeVE :: VE a -> VE (Maybe a)
        The post Scrap your type classes explores these patterns in detail (though I don’t agree with its conclusion of abolishing type classes altogether).

        The use of the type class is only to assemble a default value editor for a given type. This may or may not be useful. For instance, a pleasant default for VE (Maybe String) would be a single edit box can be left empty (so the empty string is represented as Nothing). However, you don’t get this with the default type class instance.

  4. Pingback: Composible Value Editor Code | twdkz

    • I’m not sure an applicative interface would work here. We need a bidirectional interface, ie
      A) put a value into an editor
      B) take a value out of an editor.
      I think an applicative interface might be limited to constructing values only (ie B above).

      • I don’t see how that is any different from what formlet libraries do for web forms.

      • My intuition is that with an applicative interface, putting a value into an editor (ie case A in my reply above), isn’t possible. I don’t have any experience with formlets libraries…. Looking at the example on this page:

        when I construct a form applicatively like this:

        userForm :: Form User
        userForm = User <$> name <*> inputInteger <*> input Nothing

        in providing User (::String -> String -> Date -> User) I seem to have only told the library how to construct User value out of the components. There’s no knowledge provided of how to decompose a User value into it’s components.

        Practically, Is there a function in the API that takes a value of type a and populates an existing form (of type Form a)?

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s