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
  where
    mkVE = MapVE toStruct fromStruct
        (   Label "Name" nonEmptyString
        .*. Label "Age"   mkVE
        .*. Label "Gender"   mkVE
        )
      where
        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.

A GTK UI

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!

Advertisements

Installing ghc 7.0.3 and the haskell platform on RHEL 5.6

The current haskell platform requires ghc 7.0.3. I need this to run on some RHEL 5.6 machines. Whilst this OS update was released in Jan 2011, it’s based on old software. In particular, it’s built with libc 2.5, which was released back in 2006. It’s not able to use the prebuilt generic binary release from the ghc downloads page. It says:

NOTE: If you have too old a version of libc, then you will get an error like “floating point exception” from the binaries in these bindists. You will need to either upgrade your libc (we’re not sure what the minimum version required is), or use a binary package built for your distribution instead.

I sure don’t want to upgrade libc, and to the best of my knowledge there’s no binary package built for RHEL. So, I’ll need to build it myself from source. But we need ghc to compile ghc, and to make it worse, we need a version >= 6.10, and the binaries for these won’t work with libc 2.5 either.

So, our approach needs to be:

  1. Compile and install 6.10.4 using 6.8.3
  2. Compile a binary distribution of 7.0.3 using 6.10.4
  3. Install the 7.0.3 binary distribution
  4. Compile and install the haskell platform 2011.2.0.1

But wait, as it turns out, the RHEL 5.6 C compiler (gcc 4.1.2) doesn’t seem to be compatible with recent ghc builds either, giving errors like:

rts/dist/build/RtsStartup.dyn_o: relocation R_X86_64_PC32 against `StgRun' can
not be used when making a shared object; recompile with -fPIC

(there are some details on the building and troubleshooting ghc page)

So, you need a more recent gcc also. I could have build this from source also, but luckily I had a working gcc 4.4.3 build already present.

For reference, I needed to download:

  • ghc-6.10.4-src.tar.bz2
  • ghc-6.8.3-x86_64-unknown-linux.tar.bz2
  • ghc-7.0.3-src.tar.bz2
  • haskell-platform-2011.2.0.1.tar.gz

And here’s the commands used:

# General setup
# Assumes downloaded files are in $BASE/downloads
BASE=/tmp/ghc-dev
GCC443DIR=/opt/gcc4.4.3/bin
mkdir -p $BASE/install
mkdir -p $BASE/build

# Start with a 6.8.3 binary
cd $BASE/build
tar -xjf $BASE/downloads/ghc-6.8.3-x86_64-unknown-linux.tar.bz2
export PATH=/usr/bin:/sbin:/bin
cd $BASE/build/ghc-6.8.3
./configure --prefix $BASE/install/ghc-6.8.3
make install

# Build 6.10.4 from src
cd $BASE/build
tar -xjf $BASE/downloads/ghc-6.10.4-src.tar.bz2 
export PATH=$BASE/install/ghc-6.8.3/bin:/usr/sbin:/usr/bin:/sbin:/bin
cd $BASE/build/ghc-6.10.4
./configure --prefix $BASE/install/ghc-6.10.4
make
make install

# Build 7.0.3 from src, using 6.10.4 and gcc 4.4.3
# (gcc 4.1.2 from RHEL doesn't seem to work)
cd $BASE/build
tar -xjf $BASE/downloads/ghc-7.0.3-src.tar.bz2 
export PATH=$BASE/install/ghc-6.10.4/bin:$GCC443DIR:/usr/sbin:/usr/bin:/sbin:/bin
cd $BASE/build/ghc-7.0.3
./configure
make
make binary-dist
 
# Unpack and install the 7.0.3 bin-dist
cd /tmp
rm -rf /tmp/ghc-7.0.3
tar -xjf $BASE/build/ghc-7.0.3/ghc-7.0.3-x86_64-unknown-linux.tar.bz2
cd /tmp/ghc-7.0.3
./configure --prefix $BASE/install/ghc-7.0.3
make install

# Unpack and install the haskell platform
cd $BASE/build
export PATH=$BASE/install/ghc-7.0.3/bin:$GCC443DIR:/usr/sbin:/usr/bin:/sbin:/bin
tar -xzf $BASE/downloads/haskell-platform-2011.2.0.1.tar.gz
cd $BASE/build/haskell-platform-2011.2.0.1
./configure --prefix $BASE/install/ghc-7.0.3
make
make install

Be prepared to chew up some CPU cycles! Pleasingly, once I sorted out the gcc version issue, all of the above worked without problems.

HBeat Lives

Reorganising my projects home, I copied in the old documentation for my hbeat program. The docs needed some updating, so I decided to check it all still works ok. Fearing bitrot, I was pleased and a little suprised to see that on my recently rebuilt ubuntu machine, all I needed was

sudo apt-get install libsdl1.2-dev
sudo apt-get install libsdl-mixer1.2-dev
cabal-dev install hbeat

Or at least that’s what I first thought. The program fired up ok, but failed to respond to mouse clicks as expected. It turns out that this was a pre-existing bug – if the screen redraws don’t happen fast enough, hbeat gets further and further behind in it’s event processing eventually ignoring everything. A small code fix (now published to hackage) causes out-of-date redraw requests to be dropped.

But why was I seeing this problem now? It seems that since I wrote the software, openGL via SDL seems to have got alot slower. The compositing window manager (compiz) seems to be the culprit – it’s consuming significant cpu time whilst hbeat is running. Some references to this can be found here.

I guess there’s a downside to all those fancy compositing effects.

It’s a shame hbeat is now a fair bit glitchier than it was before. Maybe sometime I’ll look at this, but for now at least it still works.

Accessing the cabal version number from an application

I wanted the –version flag in an application to return the version from the cabal file. Unable to find solution for this on the net, I ventured into the darcs source code to for a solution. It’s actually pretty easy:

Step 1

Change the Build-Type field in the cabal file to be “Custom”. This means cabal will look for a Setup.hs file to control the build.

Step 2

Create a Setup.hs that autogenerates a haskell module containing the version number. Here’s mine:

import Distribution.Simple(defaultMainWithHooks, UserHooks(..), simpleUserHooks )
import Distribution.Simple.Utils(rewriteFile)
import Distribution.Package(packageVersion)
import Distribution.Simple.BuildPaths(autogenModulesDir)
import System.FilePath((</>))
import Data.Version(showVersion)
generateVersionModule pkg lbi = do
let dir = autogenModulesDir lbi
let version = packageVersion pkg

rewriteFile (dir </> "Version.hs") $ unlines
["module Version where"
,"version :: String"
,"version = \"" ++ showVersion version ++ "\""
]

myBuildHook pkg lbi hooks flags = do
generateVersionModule pkg lbi
buildHook simpleUserHooks pkg lbi hooks flags

main = defaultMainWithHooks simpleUserHooks {
buildHook=myBuildHook
}

Step 3

Change your program to access the created Version module. It’s actually generated in the ./dist/build./autogen directory, but this seems to be correctly on the source path by default.