GUIs in Clojure, continued

37 views
Skip to first unread message

Keith Bennett

unread,
Feb 6, 2009, 5:44:13 PM2/6/09
to Clojure
This message is a response to the GUIs in Clojure thread from last
October (2008). There were "Reply to Author" links, but no "Reply"
links. Is that because of its age? The thread I'm referring to is:

http://groups.google.com/group/clojure/browse_thread/thread/22e4696550b6c4fa/fbccbe56dd45c80c?lnk=gst&q=swing

or

http://snipurl.com/bfqn6

Hello, all. Interesting thread. Sorry I'm late. ;)

I've read all the messages on the thread and have some thoughts...

A GUI, by design, is mutable. Its purpose is to enable the user to
perform actions on it that change its state, and for the program to be
able to mutate the display or other state in response to the user.
Why is this a problem?

V. Quixote said:
"It sounds like all we really need is a single clojure thread which
handles all access to Swing/AWT/Whatever."

The SwingUtilities invokeLater and invokeAndWait methods effectively
post actions to a queue which are executed sequentially by the event
dispatching thread. So this problem is solved, isn't it? Why is
there also a need for some kind of Clojure thread to coordinate this?

Pinocchio's swing macro is interesting because it enables the same
action to be called either by the event dispatching thread or another
thread, and uses the invokeAndWait when appropriate. (In some cases
you might want invokeLater but I suppose you could call invokeLater
explicitly rather than using the macro.) I can imagine this would be
helpful, for example, to trigger a refresh of the UI; but be careful
to honor model/view/controller design, which usually means calling a
model's fire method rather than telling a view to refresh itself,
because there could be multiple listeners on a model, and the model's
listener management would notify them all for you effortless and
automatically. For example, if you had data displayed in a table, and
wanted to trigger the table to update itself, you would call the
table's model's fireTableDataChanged() or the like. And, for example,
if you had a "Number of Rows" label above the table, and it had
registered itself as a listener to that table model, then it would be
notified too.

* * *

Martin DeMello said:
"In particular the developers of Monkeybars (JRuby/Swing) project have
come out and said that manually laying out widgets in code is
counterproductive."

In a substantial GUI app, it is extremely important for visual
consistency, maintainability, reliability, and extensibility for as
many components as possible to be coded once and reused (i.e. DRY).
Pretty much any repeating pattern is eligible for this, be it a
special kind of label, an entire window, or anything in between. A
problem with the use of designers is that often, because it is so easy
to create things on the fly, that reuse is often ignored. This
becomes a huge problem when something needs to be changed across the
application (e.g. adoption of user interface guidelines, or
internationalization). True, this abuse can also occur when hand
coding, but the repetition is harder to ignore.

Another problem with the use of designers is that, the last time I
checked, which was admittedly a very long time ago, it was difficult
or even impossible to use code to build the GUI. For example, have
all standard borders be of size OurGUIStandards.BORDER_SIZE, or use
the screen size calculated at runtime for a window's size and
placement.

Although hand coding Swing components is tedious, there are economies
of scale. The additional effort in keeping the code DRY pays for
itself many times over. As more reusable code is created, the
marginal cost of an additional window diminishes.

* * *

Frameworks for simplifying Swing programming can be quite useful,
especially to exploit the language's flexibility. Both Clojure and
Ruby provide ways to specify Swing code much more concisely, for
example, via lambdas and literal collection instances. However,
sometimes a framework's simplicity is provided at the cost of
functionality and flexibility. So that's something to watch out for.

- Keith

mikel

unread,
Feb 6, 2009, 6:57:02 PM2/6/09
to Clojure
On Feb 6, 4:44 pm, Keith Bennett <keithrbenn...@gmail.com> wrote:
> In a substantial GUI app, it is extremely important for visual
> consistency, maintainability, reliability, and extensibility for as
> many components as possible to be coded once and reused (i.e. DRY).

A long time ago in a galaxy far away (okay, 1992-1994 in Cupertino) I
worked on an OS project written in Dylan when it was still a lisp.
Applications were built from frames (in the sense of frame languages
for knowledge representation), and presented to the user by means of s-
expressions that described the data to be presented, the values to be
obtained from the user, and the events that were to transform the
presentation.

If you were to read through a full description of our UI system (both
implemented, and proposed but never implemented) it would probably
sort of remind you of HTML and CSS. In detail it was pretty different
(we're talking about 1992 here; HTML was new and CSS not yet
invented), but in conception, it was similar: you describe a UI
element with a set of nestable expressions, and style and configure it
with (also nestable) styles.

For example, if you wanted to display a dialog box to get some values
from the user, you could call the macro user-bind:

(user-bind ((name <string>)
(age <integer>))
(do-stuff-with-user-data name age))

user-bind expanded into code that built a dialog window, constructed
appropriate widgets to retrieve a string and a integer, and provided
an OK and cancel button. Click okay and the user-bind form continued,
evaluating the user code in an environment in which name and age were
bound to the values provided by the user; cancel and a suitable
condition was signaled.

user-bind and similar macros accepted hinting parameters to specify
what kind of widget you wanted and how to lay them out. In the longer
term the plan was to define a style language to be used like so:

(bind ((*dialog-style* (style <tool-dialog>
:scale :small
:system-font *system-dialog-
font*))
(*button-style* (style <small-button-style>
:color-scheme *dialog-bright-
color-scheme*)))
(user-bind ((name <string>)
(age <integer>))
(do-stuff-with-user-data name age)))

The idea was that the macros and methods built a UI element suitable
for capturing the events you needed. For each variable you wanted to
bind, the declared type told the system what kind of widget to create.
In cases where more than one kind of widget was appropriate, a default
choice would be provided in lexical variables that you could override
by rebinding them. Similarly, style and layout were described by
lexical variables that could be rebound. Clojure maps would be very
hand here; it would be straightforward to nest style deltas and obtain
the correct style information in a given lexical contour using merge.

Not all UIs are dialog-like, of course; so there were also functions
and macros to create longer-lived objects. A suitable combination of
input parameters and lexical bindings made the same sorts of
presentation choices, and established event-handlers for the
constructed elements.

I can imagine implementing something like those tools for Clojure
+Swing (or bare AWT, or Pivot, or SWT, or whatever). A Clojure-like
implementation might make long-lived UI elements agents. You could of
course bind such elements to variables, as was done in the system we
built; alternatively, or additionally, you could take a cue from SK8
and Symbolics Dynamic Windows, and make it possible to obtain a valid
reference to a screen object by clicking it, and then paste that
reference into code. Then update the agent to use extended or modified
styles and UI elements. Finally, ask the widget for a copy of the
environment that specifies it.

WIth such a scheme you can write UI elements as well-structured code,
interact with live objects created with the code, modify them
interactively by direct manipulation and by evaluating forms, and save
the current state of the widgets to a (presumably versioned) file.

If I were doing work like this now in Clojure, I'd be inclined to use
vectors rather than macros for most of the UI element specification.

Raoul Duke

unread,
Feb 6, 2009, 6:59:43 PM2/6/09
to clo...@googlegroups.com
> A long time ago in a galaxy far away (okay, 1992-1994 in Cupertino) I
> worked on an OS project written in Dylan when it was still a lisp.

that was a wonderful + sad story all at the same time. i'm sick and
tired of Good Enough. but don't have the time or money to go for The
Better. :-}

Reply all
Reply to author
Forward
0 new messages