Daniel, On Fri, 12 Apr 2002 00:35:55 +0200, "Daniel Fagerstrom" <[EMAIL PROTECTED]> wrote:
> Ovidiu, > > I have followed your work on Schecoon with great interest. I share the view > that structured program constructs: sequence, choice and repeat are a much > better and natural way to describe control flow in webapps, compared to > finite state machines. That said, I'm not totally enthusiastic about the > introduction of yet another program language in my webapps. But before > seeing some more realistic examples I've no strong opinions. I like that you're keeping the eyes opened ;-) Thanks for watching so far! > Concerning the continuation part: IMO the idea of letting the flow control > mechanism sending the continuation (post) address to the form pages is very > elegant, however, I'm starting to get somewhat worried about the memory > (session state) model, but hopefully I've just got it wrong. > > To the point: > > We all know that continuations handle the back button like magic, but what > about the forward button? I'll explain what's the idea below. > Ovidiu Predescu wrote: > > start -> page1 -> page2 -> page3 > > > > If the user goes back to page1 for example, or creates a new browser > > window which displays page1, then changes some values in this page and > > hits the submit button, the tree would look like this: > > > > start -> page1 -> page2 -> page3 > > \ > > ----> page2 > > > > This is a great way for the user to experiment with "what if" > > scenarios. How many times you went to Amazon and played with the > > shopping cart to see which items you can buy? This is a very good > > example of such a scenario. > > Lets instantiate your example: > > Buying a book (#1) -> buying another book (#2) -> buying still another book > (#3) -> ... (yes I like buying books :) ) ... -> giving my credit card > number (#10) -> filling in all the address info (#11) -> the commit page > (#12) > > IIUC, the continuation hash code #1 point to the state (stack) of the > program as it were when I put the first book in the shopping car, code #2 to > the program state as it were when I took the second book and so on. > > Back to the example: seeing the total price on the commit page, I suddenly > realize that I don't really need the second book. So I go back to the second > page, perform the necessary changes and hit the submit button. Now the > server start to work from the state indexed by code #2 and starts to put the > new submit info in the appropriate Schecoon variables, this is done in a new > and fresh part of the program state tree. > > book (#1) -> book (#2) -> book (#3)... -> address (#11) -> the commit page > (#12) > \ > -> book (#3a) -> ??? > > At this moment, I start to wonder how to get back to all the information I > had submitted to continuation #11, so that I can commit that info, but > without buying the second book. > > So, are "what if" scenarios really the best way to do web shopping? Or did I > completely miss the point? Or is there a better way to describe the web > shopping scenario, where the above indicated problems doesn't appear? Good question. Let's look at how such an application could be written. This is very similar with the shopping application I was thinking to implement as an example of how to program with the control flow layer. Suppose the application's entry point is the 'startShopping' function, which is called from the sitemap using the <map:call function="startShopping"/> entry. Our application displays a list of items that can be bought as links. When one of the items is clicked, we want to add it in the shopping cart, and display the list of items again. We do this until the user clicks on the "Checkout" button located on the same page. The page also displays a little shopping cart icon, which when clicked, displays the items currently in the cart. We'd have something like this then: function ShoppingSite() { this.cart = new java.util.HashMap(); } function ShoppingSite_chooseItem() { while (true) { // Initialize this by calling the business logic var itemsToBeDisplayed = ...; sendPage("chooseItems.xml", itemsToBeDisplayed); // Either one item was chosen or a view-cart or checkout happened var item = cocoon.request.getParameter("item"); // If the "item" parameter is present in the request, the user // chose an item. Otherwise consider one of the other buttons or // links has been clicked, in which case the "operation" parameter is // set. The handleOperation() function deals with this. if (item != null) { // We need to add one of the items in the basket. In case the // item is already there, add one more. cart.put(item, 1 + cart.get(item)); } else handleOperation(this, k); } } ShoppingSite.prototype.chooseItem = ShoppingSite_chooseItem; function ShoppingSite_checkout() { // Do the checkout using the "cart" instance variable } ShoppingSite.prototype.checkout = ShoppingSite_checkout; function ShoppingSite_showCart() { sendPage("showCart.html", this.cart); handleOperation(object); } ShoppingSite.prototype.showCart = ShoppingSite_showCart; function handleOperation(object) { var operation = cocoon.request.getParameter("operation"); if (operation != null && operation in object && object[operation] instanceof Function) object[operation](); } // The entry point function in the shopping site example function startShopping() { var shopper = new ShoppingSite(); shopper.chooseItem(); } The above shows a JavaScript class ShoppingSite which handles all the various operations usually associated with such an activity. The startShopping() entry point function in the sitemap creates such an instance and invokes on it the chooseItem() method. This will loop forever, waiting either for an item to be selected, or for an operation to happen. Notice the handleOperation() function which does this dispatch. To add more operations, you only need to define one more instance method to the ShoppingSite class. Now to answer your question about going back in the history and choosing a different path. Notice that the 'cart' instance variable uses a shared Java HashMap instance. This means the same instance is available to all the continuations, and any modification to it is visible from the other continuations. This means that if you go back and modify something in the page #2, the result will affect the cart in page #12. If you want to consider that once the user went back to page #2, s/he has in the cart only the item he chose in page #1, you'd have to copy, perhaps in a lazy fashion to avoid memory bloat, the 'cart' instance variable. This way each page in the tree maintains its own copy of the cart. On a different topic, take a closer look at the showCart() function. What should be the action bound to the default continuation on the showCart.html? Because this action will simply allow showCart() to return to its caller, the action on that page should be "continue shopping". You can also specify as operation 'chooseItem', but this will call chooseItem() once again, effectively adding another frame on the stack. You can avoid this problem by rewriting the chooseItem() in a tail call fashion, like this: function ShoppingSite_chooseItem() { // Initialize this by calling the business logic var itemsToBeDisplayed = ...; sendPage("chooseItems.xml", itemsToBeDisplayed); // Either one item was chosen or a view-cart or checkout happened var item = cocoon.request.getParameter("item"); // If the "item" parameter is present in the request, the user // chose an item. Otherwise consider one of the other buttons or // links has been clicked, in which case the "operation" parameter is // set. The handleOperation() function deals with this. if (item != null) { // We need to add one of the items in the basket. In case the // item is already there, add one more. cart.put(item, 1 + cart.get(item)); } else handleOperation(this, k); chooseItem(); } Apparently this causes an infinite recursion, right?! In fact this is not the case with the modified Rhino engine we're using! The interpreter simply detects this as a so-called tail function call, and avoids putting one more call frame on the stack. The advantage of writing the code like this is that you don't have to remember where functions return, and what should be assigned to the default continuation id in a page. You always specify explicit operations for all the actions, in all the forms, in all the pages. The functions will appear as not returning to their callers, but that's OK since the interpreter detects this cases and avoids creating a stack frame for the call. In case I confused you, just forget about it and use it in the old, procedural way ;-) I hope the above answers your question. Note the above code is not tested, so it may not work as it is, but this is the main idea. I'll try to take the code and write a live example of it. Please let me know if you need further clarification. Regards, -- Ovidiu Predescu <[EMAIL PROTECTED]> http://www.geocities.com/SiliconValley/Monitor/7464/ (GNU, Emacs, other stuff) --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, email: [EMAIL PROTECTED]