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 -~----------~----~----~----~------~----~------~--~---