[prev in list] [next in list] [prev in thread] [next in thread] 

List:       haskell
Subject:    Re: advice wanted on GUI design patterns
From:       "Manuel M. T. Chakravarty" <chak () is ! tsukuba ! ac ! jp>
Date:       1999-09-28 14:32:40
Message-ID: 19990928233000I.chak () is ! tsukuba ! ac ! jp
[Download RAW message or body]

Havoc Pennington <rhp@zirx.pair.com> wrote,

> On Mon, 27 Sep 1999, Antony Courtney wrote:
> > First, it is possible to write applications in Haskell using event loops
> > and mutable state.  Most of the simple toolkit bindings (such as
> > TclHaskell, which is really a binding to Tk) do this.
> 
> Is it possible to do this without implicitly keeping all your data in some
> written-in-C component (such as the GUI components themselves)? If so,
> how? (Because if not it's going to be a pretty huge spaghetti mess, it
> seems to me - plus there's not much advantage to Haskell if you can't use
> Haskell types for your data.)
> 
> It seems to me that the event-driven model requires "keeping your data" in
> a way that Haskell does not provide for, because you need to access "the
> same" data structure in all your event handlers over time, yet there is no
> way to communicate between event handlers without updating some fixed
> memory location...

[I don't know how much you know about monads, so the
following may be too verbose.]

As you definitely noticed all functions (and signal
handlers) in Gtk+HS are returning values of type `IO a' (for
some `a').  A value of type `IO a' is a "state transformer",
ie, it hides and allows the controlled manipulation of some
global state.  In the case of `IO', the state manipulated is
the same state that a C program manipulates (process-local
memory, the file system, the Internet, you name it).  The
use of `IO' allows `gtkWindowNew' to request from `libgtk'
the creation of the data structure used to represent a
window in memory and it allows `gtkWidgetShow' to advise the
X server (via `libgtk') to change the contents of the
screen.  In other words, IO is the back door from the happy
and save world of the lambda calculus to the mean and nasty
reality of the state machine, which you call your computer.

These two worlds are reconciled by regarding a state
transformer (ie, a value of type `IO a') as a function,
which, given an initial state, transforms it into a
successor state and, in addition, produces a value of type
`a', which may depend on the initial state.  In other words, 
if you are writing

  gtkWidgetShow myWindow

then this is _not_ a direct manipulation of the state of
your screen (like in C), but it denotes a function that,
when given the state of your screen, returns a new state
(the one with the window drawn on the screen[1]) - from this
perspective, everything is still functional.  Indeed, the
state-transformer-based approach to IO is not just a play of
words, it has practical consequences.  As a state
transformer is a function, you can treat it like any other
function and, for example, put it into a list,

  [gtkWidgetShow myWindow] 

The type of this list would be `[IO ()]' - and such lists
are indeed sometimes useful.  The important thing is that
putting the IO operation into a list does _not_ perform the
I/O action, ie, in the above example our window will not
(yet) appear on the screen (simply because the state
transformer was not applied to the state representing the
screen, and thus, couldn't change it) - try this in C.  The
only way to perform this application is to use the IO
function in the definition of the function `main' (which has
type `IO ()') or any (user-defined) `IO' function that is
called from `main'.

The IO operations provided by standard Haskell are
restricted to textual IO plus some very basic operating
system interaction, but as Sven already pointed out, there
are some non-standard extensions in the module `IOExts'
(provides by Hugs and GHC)[2]

  http://www.haskell.org/ghc/docs/latest/libraries/libs-9.html

which provide what you need to store your data in Haskell
data structures and still manipulate it in an imperative
style, or as Sven said,

>    * You can use Haskell like C.   :-}

The function

  newIORef :: a -> IO (IORef a)

is kind of Haskell's version of `malloc' (still everything
is fully automatically garbage collected).

  readIORef :: IORef a -> IO a

corresponds to `*' in an rvalue position and 

  writeIORef :: IORef a -> a -> IO ()

to `*' in an lvalue position (actually including the right
hand side of the assignment).  So we can define

  import IOExts

  main = do
	   counter <- newIORef 1
	   val <- readIORef counter
	   counter `writeIORef` val + 1
	   newVal <- readIORef counter
	   print newVal			-- will print `2'

(Writing `writeIORef` allows to use writeIORef as an infix
operator, making it more assignment-like :-)

C-like Haskell (or more precisely `imperative Haskell') -
while maybe the ultimate nightmare for every FP purist - is
actually still much nicer and more powerful than real C.
The over 8000 lines (incl. comments) that Gtk+HS has by now
are written in this style and I seldom got a core dump -
something that I cannot claim for my C programs :-/  The type
system still does its job of finding many bugs during
compile time and higher-order functions are as useful as
they are in a purely functional style.  Furthermore, even if
the GUI component of your application is written in
imperative Haskell, the computation parts can use a more
conventional functional style.

If you really want to now what's going when using state
transformers, I recommend

  `State in Haskell', SL Peyton Jones and J Launchbury, Lisp
  and Symbolic Computation 8(4), Dec 1995, pp293-341.
  http://research.microsoft.com/Users/simonpj/Papers/state-lasc.ps.gz

> I read the Haggis paper a while back and I'll have to revisit it. Thanks
> for the pointers from everyone that replied, I will look at FranTk. The
> problem of immediate interest to me is whether it's possible to write a
> GTK+ or GNOME application in Haskell, I've been playing with Manuel's
> bindings; if Tk can be bent into a Haskell-friendly model then perhaps
> GTK+ also could be.

There is absolutely no reason why a Haggis-like or
FranTk-like framework couldn't be build on top of Gtk+HS.
(In fact, there is somebody working on a Haggis-clone for
Gtk+HS.)  From the outset, the idea was to provide with
Gtk+HS a Haskell binding for GTK+ that keeps as close to the
C API as possible while embedding the interface nicely in
Haskell's type system - as a consequence, you get a C API in
Haskell, which requires the use of imperative Haskell.  (And
Sven has outlined how to use the `IORef's in a GUI context.)

On top of this basic binding, I would like to see some more
declarative framework(s) for implementing GUIs, but as this
is still a research issue, I think it is very important to
keep these two layers of abstraction cleanly separated (this
is in addition to the obvious software engineering benefits
of separating the two layers).  The separation should also
make it easier to maybe at some point utilise a GUI builder
like Glade in conjunction with Gtk+HS.

The Boolean Editor seems a nice example to illustrate the
use of Gtk+HS with imperative Haskell.  So, how would you
usually implement

  If you toggle the same document in one of its windows, the
  other windows should update.

in GTK+?  Using a signal handler attached to the "toggled"
event, which via its `data' argument has access to the
document data variable and to a `GSList' of windows
containing the same document, which you then traverse to
issue `gtkToggleButtonSetActive' calls on each window?
Hmm, the problem is that these would again lead to toggle
events.  So, what's the most elegant way to do it in C and
GTK+ (diabling the events for the update seems a bit gross)?

Hope that clarified things a bit,

Manuel

[1] I know that GTK+ is actually more complicated and
    `gtkWidgetShow' returns before the widget is actually
    drawn, but that's not important here.

[2] `IOExts' is non-standard, but so is every foreign
    function interface for accessing C libraries at the
    moment.  Definitely an issue that has to be addressed in 
    the next version of the language.



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic