Thank you.  Makes perfect sense to me now.

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

>
> 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>
> >
> >
> >
> > > 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-patter.
> ..
> > > )
> >
>

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To post to this group, send email to clojure@googlegroups.com
To unsubscribe from this group, send email to 
clojure+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to