cocoon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Ovidiu Predescu <ovi...@apache.org>
Subject Re: continuation fear (was Re: [status & RT] design challenges)
Date Fri, 12 Apr 2002 05:26:43 GMT
Daniel,

On Fri, 12 Apr 2002 00:35:55 +0200, "Daniel Fagerstrom" <daniel.fagerstrom@swipnet.se>
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 <ovidiu@apache.org>
http://www.geocities.com/SiliconValley/Monitor/7464/ (GNU, Emacs, other stuff)

---------------------------------------------------------------------
To unsubscribe, e-mail: cocoon-dev-unsubscribe@xml.apache.org
For additional commands, email: cocoon-dev-help@xml.apache.org


Mime
View raw message