Chatting with Santiago earlier we kicked around some ideas on doing modal webapps in ~pure java. Getting back-button compatible behavior isn't too bad by using a StateRegistry
type system and requiring any view state be stored in a registerable component.
Getting linear style scripting is a lot trickier though, let's say we want this flow:
def request_free_icecream()
name = ask_for :name
address = ask_for :address
unless blacklist.check_for_idjits(:name => name, :address => address )
send_icecream_to(name, address)
else
return "rejected"
end
return "accepted"
end
In this case ask_for(symbol)
entails a request/response (possibly multiple if validation fails). The flow of what is going on is easy to see. Now, modeling this in a typical webapp might be:
def request_free_icecream()
@session['icecream-request'] = IcyAccumulator.new
end
def set_name
accum = @session['icecream-request']
accum.name @params['name']
end
def set_address
accum = @session['icecream-request']
accum.name @params['address']
finished()
end
def finished
accum = @session['icecream-accumulator']
unless blacklist.check_for_idjits(:name => name, :address => address )
send_icecream_to(accum.name, accum.address)
redirect_to :controller => :globals, :action => :accepted
else
redirect_to :controller => :globals, :action => :rejected
end
@session['icecream-accumulator'] = nil
end
Now, aside from the fact that the second example actually works (unlike the first which is a variety of pseudocode called "ideacode"), it is way harder to follow what is actually happening. Nothing new here, modal webapp proponents have been flogging this horse for years. Now, let's pretend the above code snippets are Java, and as we know we cannot use the standard tool of continuations to build this out, how can we do it?
One option, which Torsten has done, is to transform the code at the bytecode level to emulate continuations. This is the ~classic way, but isn't the only way. It is a bit of a pita to do well, I think (I haven't tried, have just watched people doing it) -- probably because java bytecode doesn't lend itself to CPS style transforms.
What we need to do is effectively transform the first snippet into the second snippet. To do this it needs to be split into the following chunks:
... {
invoke ask_for :name
}
... {
assign result to :name
invoke ask_for :address
}
... {
assign result to :address
unless blacklist.check_for_idjits(:name => name, :address => address )
send_icecream_to(name, address)
else
return "rejected"
end
return "accepted"
}
Which splits the code into the actions in the second code example. Need to be able to match the points at which control flow goes back to the user (ie, send a response and wait) and transform "what comes next" from a thread of execution into a function (functor) which can be invoked, so to look at it again, we would want a transform along the lines of:
one = functor do
name = ask_for :name
end
two = functor do
address = ask_for :address
end
three = functor do
unless blacklist.check_for_idjits(:name => name, :address => address )
send_icecream_to(name, address)
else
#return "rejected"
return_result "rejected"
end
#return "accepted"
return_result "accepted"
end
We now have three functors (some arm waving, bear with me) which encapsulate the flow we described. We will presume "correct closures" as Matz describes them (and groovy implements now, but ruby doesn't yet AFAIK), where the name
and address
variables are available to the functor being created and assigned to three
(right now they'd be undefined in ruby as they are scoped to the box and don't fall through to the outer scope).
Now, we've hit the point where we need to see what ask_for
actually looks like. I don't know. It would need to set up a page, fetch the results, and return the request params as an array. Probably it maps the argument to a view of some sort. This is all very cocoon.sendPageAndWait(..)
-ish. The key point is that it passes through a single point to ask for a view rendering and announce it wants the results back. You can pinpoint what this chunk of code is.
It is a definable pointcut =)
Now, one of the (few) accepted patterns in AspectJ is a functor transform as I call it, or a worker object creation pattern as Ramnivas calls it. His name is better, but I think in terms of mine anyway (tangent, I am really looking forward to Raminvas's next book!). In this case you need to match the entrance and exit of the flow blocks, which can be nicely demarcated by naming conventions and calls to the "I need a request cycle" function.
It helps if each functor knows its "next", so we can alter it to create:
sequence = functor do
name = ask_for :name
end
sequence = sequence.next = lambda do
address = ask_for :address
end
sequence = sequence.next = lambda do
unless blacklist.check_for_idjits(:name => name, :address => address )
send_icecream_to(name, address)
else
#return "rejected"
return_result "rejected"
end
#return "accepted"
return_result "accepted"
end
Where sequence
is the name of our functor, and each one knows its next
. There is an awkward bit where the next=
receives a proc instead of a functor, this is because it needs to act as a functor generator so that multiple calls to it create new functor instances. Until really implemented, this is just a guess on my part though.
Now, I can see in my head implementing this transform via AspectJ, but it'll take several hours at a minimum to go from rubyesque ideacode to Java/AspectJ implementation, so I am going to put that off. Pretend we have it, and have a useful Continuable
implementation which is the functor stuff in the ideacode. Heck, the transform to a worker object may be viciously hard in this case, is just idea stuff right now.
We need to store these continuables in the session, probably under a map just like the continuations in FlowScript, and be able to call back to them based on continuation ids, we would need some convenient mechanism for managing them. That is a worry for another day -- right now we will treat it like naive C memory management (ie, none -- don't let sessions live long). Probably a sweeper-like system will make the most sense, but that is just intuition.
What we kinda did was kinda described a kinda implementation of a (broken and very limited) form of the continuation passing transform via aspects.