The Application Context Pattern

862 views
Skip to first unread message

Itay Maman

unread,
Feb 27, 2009, 3:05:19 AM2/27/09
to Clojure
Some of the reaction for Waterfront was related to the Application
Context Pattern (ACP) - The pattern that allows most of Waterfront's
code to be purely functional. I'll try to explain the basics in this
post. Let me start with the motivation: the reason why FP is at odds
with GUI code.

(Pure) Functional code has no side effects, which implies immutability
of state. There are no fields nor global variables that can be
assigned to. Thus, one function can affect the computation carried out
by another function only by the passing of parameters. Most GUI
systems are built around the notion of event handlers which are
invoked by a message processing loop. There is no chain of calls from
one event handler to another.
In particular, if handler "A" computed some new value it cannot pass
it on to handler "B" because the system will call "B" only after "A"
returns. That's the predicament.

ACP overcomes this by capturing the applications current state in an
immutable map. All event handlers receive a single parameter which is
the "current" context and compute the "new" context. A typical handler
(henceforth: "context processing function") will carry out these
activities: (a) Examine the current context; (b) Perform some GUI
operations (setSize, setText, etc.); (c) Compute a new context based
on the current context and on information obtained from the GUI
(getText, etc.). The caller (henceforth: "dispatcher") takes the
returned context and will use it as the new current context, the next
time a context processing function is invoked.

This means that when you register event handler with a Swing widget
the handler needs to to call the ACP dispatcher passing it a context
processing function.

The net effect of this approach is that only the dispatcher has to
deal with mutable state. The context processors are functional: they
merely compute the new state from the current.

application-context-pattern.clj (http://groups.google.com/group/
clojure/web/application-context-pattern.clj) shows a concrete example.
It's about 140 LOC (ripped off from the real Waterfront codebase)
structured as follows:
Lines 1..40: General-purpose helpers.
Lines 40..90: The ACP infrastructure
Lines 90..140: A quick sample, built around ACP.

The sample program opens a JFrame with two buttons: Input and Output.
A click on the input button will pop-up an input dialog box. A click
on the output button will pop-up a message box showing the last value
entered into the input box. There's also a JLabel showing the length
of the input, but let's ignore it for the moment.

The entry point into the ACP world is the bootstrap function. It takes
two parameters: a context processing function and an initial context.
In the example, this is carried out at the bottom of the run-it
function:

(defn run-it []
(let [build-ui (fn [ctx]
(let [f (javax.swing.JFrame. "Frame")
b-in (javax.swing.JButton. "Input")
b-out (javax.swing.JButton. "Output")]

(.addActionListener b-in (new-action-listener (fn [event]
((ctx :dispatch) get-input))))

(.addActionListener b-out (new-action-listener (fn [event]
((ctx :dispatch) show-output))))

(.setLayout f (java.awt.FlowLayout.))
(doseq [x [b-in b-out]]
(.add f x) )

(doto f
(.setSize 500 300)
(.setDefaultCloseOperation javax.swing.JFrame/
DISPOSE_ON_CLOSE)
(.setVisible true))

(assoc ctx :frame f) ))]

(invoke-later #(bootstrap build-ui {})) ))


invoke-later is a utility function that is mapped to SwingUtilities/
invokeLater.

Let's drill down into the build-ui function: It takes the current
context (ctx parameter). Then it creates the frame and the buttons. It
uses new-action-listener (another utility) to register an action
listener with the buttons. The first listener looks like this:
((ctx :dispatch) get-input))))

It uses (ctx :dispatch) to obtain the dispatcher from which ctx was
obtained, and evaluates it passing get-input as the context processing
function. The call to bootstrap initialized this dispatcher and added
the :dispatch mapping to the initial context.

get-input looks like this:
(defn- get-input [ctx]
(let [reply (javax.swing.JOptionPane/showInputDialog nil "Type in
something")]
(assoc ctx :user-input reply) ))

It pops-up an input box, and returns a new context which is the same
as the current context except that :user-input is now mapped to value
returned from the input box.

show-output is the context processing function for the output button:
(defn- show-output [ctx]
(javax.swing.JOptionPane/showMessageDialog nil (ctx :user-
input)) )

Note that show-output returns nil which the dispatcher interprets as
"no change to ctx".

What we have is that get-input communicates with show-output by
returning a new context. There's no assignment into atoms or the
likes. The mutable state is encapsulated within the dispatcher.

It is now time for the JLabel to step into the plate. We now want to
add a JLabel that will show the length of the user input. We want this
label to be updated on the fly, with no explicit user request. This
type of behavior is common in GUI applications. To this end, the
dispatcher also supports the notion of observers. In ACP an observer
is a function takes two contexts: old-ctx and new-ctx.

An observer typically compares the contexts. If the mappings it is
interested in were changed, it carries out these activities:
(a) Updates the GUI (setText, setEnabled, etc.); (b) Computes a new
context (that is: a context that is even newer than new-ctx). After a
context processing function was invoked, the dispatcher will run all
observers in a loop until a steady state was reached.

Here's the observer that takes care of updating the label:
(defn- input-observer [old-ctx new-ctx]
(when-not (= (old-ctx :user-input) (new-ctx :user-input))
(.setText (new-ctx :label) (str "Input length: " (count (new-
ctx :user-input)))) ))

One register an observer by adding it to (ctx :observers):
(assoc ctx :frame f :label label :observers (cons input-
observer (ctx :observers)))

There all sort of variations on these theme and some subtleties, but I
want to keep this post coherent so I'll skip these for now. Anyway,
all the fundamentals are here. As you can see this pattern is quite
powerful. Almost all functionality of Waterfront is built on top of
this.

For instance, the menu system is described as a DSL (a vector of maps)
mapped to the :menu key of the context. An observer keeps an eye on
the :menu mapping and rebuilds the JMenuBar whenever it changes. Also,
the plugin-loader is an observer that watches the :plugins list. When
a new entry is found, it loads this plugin. In fact, in Waterfront,
the startup function simply registers the plugin observer and loads
the list of plugins from a file.


(file: http://groups.google.com/group/clojure/web/application-context-pattern.clj)

linh

unread,
Feb 27, 2009, 5:24:39 AM2/27/09
to Clojure
thanks, this will be very useful for me
> (file:http://groups.google.com/group/clojure/web/application-context-patter...)

Jeffrey Straszheim

unread,
Feb 27, 2009, 9:18:31 AM2/27/09
to clo...@googlegroups.com
I've seen the term "skyhook" used to describe a very similar system.  In any event, it looks cool.

Marko Kocić

unread,
Feb 27, 2009, 9:38:38 AM2/27/09
to Clojure
Interesting approach, nice explained.

Does anyone have similar example using one of the cells
implementations?
What would be pros/cons between this and cells approach?

Regards,
Marko Kocić

Andy Chambers

unread,
Feb 27, 2009, 12:59:23 PM2/27/09
to clo...@googlegroups.com
2009/2/27 Marko Kocić <marko...@gmail.com>:

>
> Interesting approach, nice explained.
>
> Does anyone have similar example using one of the cells
> implementations?
> What would be pros/cons between this and cells approach?

The cells implementations I've seen posted to this list (in fact pretty much all
cells reimplementations) haven't yet approached the sophistication of
kenny-cells.

It's like editors. Everyone reckons they can make one better than emacs (or
vi) but underestimate the man-hours that have gone into them over the years
and end up with something that maybe looks as good (or better) superficially
but when you delve into the details, just doesn't cut the mustard.

I'd love to see some kind of clojure-cells but it's still a long way off.

Stuart Sierra

unread,
Feb 27, 2009, 1:41:57 PM2/27/09
to Clojure
On Feb 27, 12:59 pm, Andy Chambers <achambers.h...@googlemail.com>
wrote:
> The cells implementations I've seen posted to this list (in fact pretty much all
> cells reimplementations) haven't yet approached the sophistication of
> kenny-cells.

Agreed. And I wrote two of them. They're just toy implementations so
far.

-Stuart Sierra

Marko Kocić

unread,
Feb 27, 2009, 4:13:44 PM2/27/09
to Clojure
> > The cells implementations I've seen posted to this list (in fact pretty much all
> > cells reimplementations) haven't yet approached the sophistication of
> > kenny-cells.
>
> Agreed.  And I wrote two of them.  They're just toy implementations so
> far.

I'm still not sure how it should look like given clojure immutablility.

samppi

unread,
Feb 27, 2009, 7:26:30 PM2/27/09
to Clojure
It looks really nice. I have a question about those observers, though--
every time that a context-processing function is called, every
observer is called one by one, no matter what the context-processing
function was. This seems somewhat inefficient, more so than listeners
that listen to only certain functions and are called when the listened
function is activated. In your experience, is this not a big problem?
Or am I missing something?
> (file:http://groups.google.com/group/clojure/web/application-context-patter...)

Itay Maman

unread,
Feb 28, 2009, 4:54:28 AM2/28/09
to Clojure


On Feb 28, 3:26 am, samppi <rbysam...@gmail.com> wrote:
> It looks really nice. I have a question about those observers, though--
> every time that a context-processing function is called, every
> observer is called one by one, no matter what the context-processing
> function was. This seems somewhat inefficient, more so than listeners
> that listen to only certain functions and are called when the listened
> function is activated. In your experience, is this not a big problem?

I believe that in many practical scenarios this is not a major
problem. Most observers will exit
almost immediately so iterating over them (even several hundreds of
them) will not be too expensive.

Anyway, if it does get into a problem I can think of two possible
solutions:

1) It is possible to create more than one context in a program (in
other words: the context
is not a singleton). By splitting the contexts the number of observers
per invocation of a context-processor
will get smaller.

2) It is possible to create one observer (parent) that delegates to
other observers (children). The parent will contain the logic as to
whether the children need to be invoked. If it decides that the answer
is "No" it will immediately return, thus reducing the workload on the
observer invocation loop.


-Itay

Itay Maman

unread,
Feb 28, 2009, 10:38:16 AM2/28/09
to Clojure


On Feb 27, 5:38 pm, Marko Kocić <marko.ko...@gmail.com> wrote:
> Interesting approach, nice explained.
>
> Does anyone have similar example using one of the cells
> implementations?
> What would be pros/cons between this and cells approach?

I think that in general, when comparing powerful facilities (such as
the Context-Pattern or Cells) one usually ends up with the conclusion
that they are largely equivalent: the former can be implemented in
terms of the latter and vice-versa. Thus, a discussion of pros and
cons is more about stylistic issues: how hard is it to achieve a
certain behavior in the two approaches?

With that in mind I think that cells are the natural choice for
implementing a complicated net of (well...) spread-sheet-like cells.

Contexts are more natural for UI since the observers are ran only
after a processor has finished. Even if the processor alters several
values in the context, the observers will be notified only when the
processor returns, seeing all the changes bundled together. In many
cases this is the desired behavior in UI apps. If a single user
request adds 100 strings to a list you want the corresponding widget
to be updated once, not 100 times.

I think the differences boil down to the fact the processors are
functional in their nature: the processor per-se does not notify the
observers, only the dispatcher does. Thus, you can compose two
processors (in the same manner you compose functions) without worrying
of side effects due to observers being fired.

-Itay


>
> Regards,
> Marko Kocić

CuppoJava

unread,
Feb 28, 2009, 12:32:30 PM2/28/09
to Clojure
Hi Itay,
I'm a little confused about one aspect of the context pattern.

If I understand this write, a listener is a function that takes an old
context, and returns a new context, BUT it also calls the appropriate
GUI functions (setText, setSize) to ensure that the gui state is
consistent with the new context. Is that correct?

If so then the listeners aren't purely functional. They don't affect
any mutable state in atoms or refs, but they mutate the current
running state of the GUI. So side-effects of these functions are
therefore important.

Itay Maman

unread,
Feb 28, 2009, 1:22:46 PM2/28/09
to Clojure
Hi,

On Feb 28, 8:32 pm, CuppoJava <patrickli_2...@hotmail.com> wrote:
> Hi Itay,
> I'm a little confused about one aspect of the context pattern.
>
> If I understand this write, a listener is a function that takes an old
> context, and returns a new context, BUT it also calls the appropriate
> GUI functions (setText, setSize) to ensure that the gui state is
> consistent with the new context. Is that correct?

Yes.

>
> If so then the listeners aren't purely functional. They don't affect
> any mutable state in atoms or refs, but they mutate the current
> running state of the GUI. So side-effects of these functions are
> therefore important.


Processors/observers may mutate the UI. Yet, even if the processors
manipulate the UI, they will not cause observer-issued side effects.
Also, the affect of either processors or observers is limited to the
UI - their behavior WRT to the program-manged state is side-effect
free.

Thanks for bringing this up, I wasn't very clear.

-Itay

Glen Stampoultzis

unread,
Mar 4, 2009, 1:45:28 AM3/4/09
to clo...@googlegroups.com
Hi Itay,

Thanks for posting this example.  Being new to Clojure it's a nice example to study since it solves a very realistic problem that many new to functional programming will face.

I think I've unraveled most of how the code is working but there's one function I'm not particularly clear about.

(defn- run-observers-till-fixpoint [prev next]
  (let [observers (next :observers)
       new-next (run-observers prev next observers)]
    (if (= new-next next)
      new-next
      (recur next new-next) )))

While I understand run-observers, run-observers-till-fixpoint has be baffled.  Why is this required?


2009/2/27 Itay Maman <itay....@gmail.com>

Itay Maman

unread,
Mar 4, 2009, 8:02:06 AM3/4/09
to Clojure
Suppose you have three observers: o1, o2, o3. run-observers evaluates
them in this order.
Let's assume we don't have run-observers-till-fixpoint. Thus, after
the evaluation of a processor we will use run-observers to run these
observers.
Under this scenario, what will happen if o3 will return a new context?
o1 and o2 were already evaluated so they will not be able to react to
the updates in the context issued by o3.

run-observers-till-fixpoint takes care of that by repeatedly looping
thru all observers until the context stabilizes. This has the
disadvantage of a possible infinite loop, but the advantage of making
the context pattern indifferent of the order of the observers. My
experience shows that observers ordering issues are more complicated
to solve than infinite loops among observers.

--
Itay Maman
http://javadots.blogspot.com/



On Mar 4, 8:45 am, Glen Stampoultzis <gst...@gmail.com> wrote:
> Hi Itay,
> Thanks for posting this example.  Being new to Clojure it's a nice example
> to study since it solves a very realistic problem that many new to
> functional programming will face.
>
> I think I've unraveled most of how the code is working but there's one
> function I'm not particularly clear about.
>
> (defn- run-observers-till-fixpoint [prev next]
>   (let [observers (next :observers)
>        new-next (run-observers prev next observers)]
>     (if (= new-next next)
>       new-next
>       (recur next new-next) )))
>
> While I understand run-observers, run-observers-till-fixpoint has be
> baffled.  Why is this required?
>
> 2009/2/27 Itay Maman <itay.ma...@gmail.com>
> >http://groups.google.com/group/clojure/web/application-context-patter...
> > )

Glen Stampoultzis

unread,
Mar 4, 2009, 3:05:27 PM3/4/09
to clo...@googlegroups.com
Thank you.  Makes perfect sense to me now.

2009/3/5 Itay Maman <itay....@gmail.com>

Krešimir Šojat

unread,
Mar 10, 2009, 3:34:55 PM3/10/09
to Clojure

> Does anyone have similar example using one of the cells
> implementations?
> What would be pros/cons between this and cells approach?

Sorry for a late reply but some of the code didn't exist when you
asked your question. This example can be done using neman cells and
swing wrapper like this: http://is.gd/mKRT

This shows that swing wrapper can 'erase' lexical scope so you can use
widgets before they are declared and 'bind' macro, part of cells lib,
that will sync text property of label with value of user-input cell.

Libs are very experimental, so this is more a feature preview for now,
not something you would use just yet.

--
Krešimir Šojat
Reply all
Reply to author
Forward
0 new messages