Confused about with-flow and the login widget.

54 views
Skip to first unread message

Robin Lee Powell

unread,
Oct 13, 2008, 3:34:36 AM10/13/08
to Weblocks

From http://www.defmacro.org/ramblings/continuations-web.html and
the Tinaa documentation, I believe that this should work:

(with-flow
self-widg
(if (yield
(make-instance
'login
:on-login #'check-login))
"Go You."))

What actually happens is that the login widget shows up, check-login
DTRT (that is, if it returns nil the login widget bitches), but when
I give the correct login information, I just get back to the login
widget again, instead of getting the "Go You." page.

-Robin

--
They say: "The first AIs will be built by the military as weapons."
And I'm thinking: "Does it even occur to you to try for something
other than the default outcome?" -- http://shorl.com/tydruhedufogre
http://www.digitalkingdom.org/~rlpowell/ *** http://www.lojban.org/

Robin Lee Powell

unread,
Oct 13, 2008, 4:28:38 AM10/13/08
to Weblocks
On Mon, Oct 13, 2008 at 12:34:36AM -0700, Robin Lee Powell wrote:
>
>
> >From http://www.defmacro.org/ramblings/continuations-web.html and
> the Tinaa documentation, I believe that this should work:
>
> (with-flow
> self-widg
> (if (yield
> (make-instance
> 'login
> :on-login #'check-login))
> "Go You."))
>
> What actually happens is that the login widget shows up,
> check-login DTRT (that is, if it returns nil the login widget
> bitches), but when I give the correct login information, I just
> get back to the login widget again, instead of getting the "Go
> You." page.

Testing by format statements, I've discovered that the code *is*
working correctly; that is the if is being evaluated properly, and
the "Go You." *is* reached... And then the widget is re-rendered
and the with-flow is re-entered and the user is presented with the
login widget again.

-_-

The with-flow example at
http://www.defmacro.org/ramblings/continuations-web.html makes it
pretty clear that the last thing in the with-flow should completely
replace the widget that is with-flow's first argument, but that's
very much not happening. Have I got a bug with with-flow here, or
am I doing it wrong, or both?

Leslie P. Polzer

unread,
Oct 13, 2008, 5:06:49 AM10/13/08
to webl...@googlegroups.com

What's the full code?

nunb

unread,
Oct 13, 2008, 3:58:22 PM10/13/08
to weblocks
I have been sitting on some basic weblocks intro stuff. I posted what
I have about flows to nunb2.blogspot.com

still getting my head wrapped around .. dialog.lisp workflow.lisp
and the whole callanswer protocol.

On 13 Ott, 10:28, Robin Lee Powell <rlpow...@digitalkingdom.org>
wrote:
> On Mon, Oct 13, 2008 at 12:34:36AM -0700, Robin Lee Powell wrote:
>
> > >Fromhttp://www.defmacro.org/ramblings/continuations-web.htmland
> > the Tinaa documentation, I believe that this should work:
>
> > (with-flow
> > self-widg
> > (if (yield
> > (make-instance
> > 'login
> > :on-login #'check-login))
> > "Go You."))
>
> > What actually happens is that the login widget shows up,
> > check-login DTRT (that is, if it returns nil the login widget
> > bitches), but when I give the correct login information, I just
> > get back to the login widget again, instead of getting the "Go
> > You." page.
>
> Testing by format statements, I've discovered that the code *is*
> working correctly; that is the if is being evaluated properly, and
> the "Go You." *is* reached... And then the widget is re-rendered
> and the with-flow is re-entered and the user is presented with the
> login widget again.
>
> -_-
>
> The with-flow example athttp://www.defmacro.org/ramblings/continuations-web.htmlmakes it

Robin Lee Powell

unread,
Oct 13, 2008, 5:49:42 PM10/13/08
to webl...@googlegroups.com
I tried to read that, but the lack of code indentation just makes
the long bits too much for me.

-Robin

Robin Lee Powell

unread,
Oct 13, 2008, 5:56:53 PM10/13/08
to webl...@googlegroups.com
On Mon, Oct 13, 2008 at 11:06:49AM +0200, Leslie P. Polzer wrote:
>
>
> What's the full code?

Attached. Output is:

- ----------

About to flow.
[here I login]
check-login: rlpo...@digitalkingdom.org -- password
user : #<MY-APP::USER {10044B7091}>
Yield true.
About to flow.

- -------------

code.txt

Robin Lee Powell

unread,
Oct 13, 2008, 8:18:42 PM10/13/08
to webl...@googlegroups.com
On Mon, Oct 13, 2008 at 02:56:53PM -0700, Robin Lee Powell wrote:
> On Mon, Oct 13, 2008 at 11:06:49AM +0200, Leslie P. Polzer wrote:
> >
> >
> > What's the full code?
>
> Attached.

The redirect back to the same page, by the way, is because the login
widget doesn't appear without a reload if that's not there.

Robin Lee Powell

unread,
Oct 13, 2008, 8:34:31 PM10/13/08
to webl...@googlegroups.com
On Mon, Oct 13, 2008 at 02:56:53PM -0700, Robin Lee Powell wrote:
> On Mon, Oct 13, 2008 at 11:06:49AM +0200, Leslie P. Polzer wrote:
> >
> >
> > What's the full code?
>
> Attached.

And here's the version that works. Note the second yield, that the
with-flow example in
http://www.defmacro.org/ramblings/continuations-web.html simply
doesn't have at all.

The redirect is required after *both* yields, or the page is simply
blank. That's ... unfortunate. Can someone who understands those
bits take a look at it?

code2.txt

Robin Lee Powell

unread,
Oct 14, 2008, 2:37:34 AM10/14/08
to webl...@googlegroups.com
I want to point something out about this code.

On Mon, Oct 13, 2008 at 02:56:53PM -0700, Robin Lee Powell wrote:
> (let* ((nav
> (make-navigation
> 'main-menu
> 'self (make-instance 'composite :widgets (list
> (let ((self-widg (make-instance 'composite)))
> (setf (composite-widgets self-widg)
> (list
> (lambda () "test1")
> (lambda ()
> (format t "About to flow.~%")
> (with-flow
> self-widg
> (cond
> ((yield
> (make-instance
> 'login
> :view 'my-login-view
> :on-login #'check-login))
> (format t "Yield true.~%")
> "Go You.")))
> (redirect "/self"))))

That redirect is *after* the with-flow, but still inside the lambda.
As far as I can tell, it is reached at every pass through the
lambda. The with-flow stuff, and pretty much everything else
actually, in
http://www.defmacro.org/ramblings/continuations-web.html seem to say
that that shouldn't happen: that with-flow or do-widget act like
function calls, and execution stops at that point.

What's up with that?

Stephen Compall

unread,
Oct 14, 2008, 4:06:06 PM10/14/08
to webl...@googlegroups.com
Robin Lee Powell <rlpowell-VkGw2YyLwTCa/A/roGhosh2...@public.gmane.org> writes:
> (setf (composite-widgets self-widg)
> (list
> (lambda () "test1")
> (lambda ()
> (format t "About to flow.~%")
> (with-flow

Let's dissect this in two parts:

1. What is different about your code versus Slava's example that makes
yours not work?

2. What combination of side-effects made the alternative with a second
yield appear to work?

== Why is yours different?

The with-flow creates a new flow. To be more specific, *every time a
WITH-FLOW form is evaluated, it creates a new flow based on replacing
its widget argument*.

Slava's example is intended for inclusion in an action lambda. However,
a rendering function can be called multiple times on-demand. So here is
what happened:

1. Widget using with-flow rendered. This is repeatable; each render
simply restarts the flow.

2. WITH-FLOW form entered.

3. WITH-FLOW calling widget *replaced*; a continuation that restores it
is stored in the replacement.

4. Login repeatably rendered.

5. Login escapes to the continuation, which restores the widget that
uses with-flow in rendering.

6. This widget is rerendered, restarting the flow.

== Why did the alternative work?

Let's look at a snippet of that as well:

> (t
> (yield (make-instance 'composite :widgets (list "Go You."))))))

By replacing the widget that uses WITH-FLOW in rendering with another
widget reporting success, you arranged a similar circumstance to that
for #4 above; it has been replaced with the so-yielded composite.

== What else could I do?

I've written a working version of your second version. Well probably; I
didn't test it or anything.

(defwidget login-maybe (login)
()
(:documentation "Render login form only if not logged in."))

(defmethod initialize-instance ((self login-maybe) &key &allow-other-keys)
(setf (widget-continuation self)
(lambda (&optional auth)
(declare (ignore auth)) ;unless you care...
(mark-dirty self))))

(defmethod render-widget-body ((self login-maybe) &key &allow-other-keys)
(if (authenticatedp)
(render-widget "Go You." :inlinep t)
(call-next-method)))

;; where your nav gets generated...


(make-navigation
'main-menu
'self (make-instance 'composite

:widgets (list (make-instance 'login-maybe :view 'my-login-view
:on-login #'check-login))))

You could get really cute and define a method on `widget-continuation'
that adds that mark-dirty call to whatever's really in there. You could
also more-or-less avoid defining a new widget class with the same (setf
widget-continuation) thing, but you'd lose clarity, self-documentyness,
and reusability (e.g. parameterize the bit where it renders "Go You.").

--
I write stuff at http://failex.blogspot.com/ now. But the post
formatter and themes are terrible for sharing code, the primary
content, so it might go away sooner or later.

Stephen Compall

unread,
Oct 14, 2008, 4:16:08 PM10/14/08
to webl...@googlegroups.com
Robin Lee Powell <rlpowell-VkGw2YyLwTCa/A/roGhosh2...@public.gmane.org> writes:
> I want to point something out about this code.
> That redirect is *after* the with-flow, but still inside the lambda.
> As far as I can tell, it is reached at every pass through the
> lambda. The with-flow stuff, and pretty much everything else
> actually, in
> http://www.defmacro.org/ramblings/continuations-web.html seem to say
> that that shouldn't happen: that with-flow or do-widget act like
> function calls, and execution stops at that point.
>
> What's up with that?

CL-CONT implements delimited continuations, so the continuability is
only valid lexically within a with-call/cc form (which with-flow expands
to). Wrap additional code you want to be part of the continuability
context of a with-flow with an additional with-call/cc; lambda/cc is a
shortcut for (with-call/cc (lambda ...)).

Robin Lee Powell

unread,
Oct 15, 2008, 3:57:57 PM10/15/08
to webl...@googlegroups.com
On Tue, Oct 14, 2008 at 03:06:06PM -0500, Stephen Compall wrote:
> WITH-FLOW form is evaluated, it creates a new flow based on
> replacing its widget argument*.

D'oh.

> Slava's example is intended for inclusion in an action lambda.

Just for the record, it doesn't *say* that anywhere in that article.
I mean, you're obviously correct, but there was no way to know that
from that example.

> == What else could I do?
>
> I've written a working version of your second version. Well
> probably; I didn't test it or anything.

Thank you very much!

Now, if someone could just tell my why my with-flow stuff never
renders without a reload... nunb had the same problem.

Robin Lee Powell

unread,
Oct 15, 2008, 3:59:54 PM10/15/08
to webl...@googlegroups.com
On Tue, Oct 14, 2008 at 03:06:06PM -0500, Stephen Compall wrote:
> The with-flow creates a new flow. To be more specific, *every time a
> WITH-FLOW form is evaluated, it creates a new flow based on replacing
> its widget argument*.
>
> Slava's example is intended for inclusion in an action lambda. However,
> a rendering function can be called multiple times on-demand. So here is
> what happened:
>
> 1. Widget using with-flow rendered. This is repeatable; each render
> simply restarts the flow.
>
> 2. WITH-FLOW form entered.
>
> 3. WITH-FLOW calling widget *replaced*; a continuation that restores it
> is stored in the replacement.
>
> 4. Login repeatably rendered.
>
> 5. Login escapes to the continuation, which restores the widget that
> uses with-flow in rendering.
>
> 6. This widget is rerendered, restarting the flow.

For the record, what I had assumed from Slava's example is that
there was book-keeping in place to make with-flow a one-time thing;
that is, once a widget was replaced by with-flow, the system would
remember that it had been replaced and show the new widget forever
until answer was called.

That seems more intuitive to me; is it feasible/worth implementing?

Ian Eslick

unread,
Oct 15, 2008, 11:10:56 PM10/15/08
to webl...@googlegroups.com
It sounds like you're proposing to make with-flow work inside
rendering functions.

I don't think this is a good idea. There are a number of assumptions
in the system that side effects to the widget hierarchy are only made
in actions, not in the rendering pipeline. The rendering pipeline is
assumed to be entirely idempotent given the same DB state. I'd expect
there to be alot of bugs falling out of this one change and making a
wholesale change in policy would be pretty expensive.

I was originally somewhat frustrated by this limitations, but over
time learned the intended usage model and haven't had much problems
with this restriction since.

Ian

Robin Lee Powell

unread,
Oct 15, 2008, 11:24:19 PM10/15/08
to webl...@googlegroups.com
Good to know; I don't know the internals at all.

So (and I apologize if I'm opening a huge can of worms here) what
counts as an "action"?

-Robin

Ian Eslick

unread,
Oct 16, 2008, 12:02:41 AM10/16/08
to webl...@googlegroups.com
There should be an e-mail in the archive with some detail on this, but
the short answer is any closure passed to 'render-link' or the
equivalent.

(render-link (lambda (&rest args)
(declare (ignore args))
(with-flow old-inst ...))
"This is a link that does a flow")

Stephen Compall

unread,
Oct 16, 2008, 12:23:38 AM10/16/08
to webl...@googlegroups.com
Robin Lee Powell <rlpowell-VkGw2YyLwTCa/A/roGhosh2...@public.gmane.org> writes:
> So (and I apologize if I'm opening a huge can of worms here) what
> counts as an "action"?

You can also treat the call to `init-user-session' as side-effecty.

Stephen Compall

unread,
Oct 16, 2008, 12:26:43 AM10/16/08
to webl...@googlegroups.com
Robin Lee Powell <rlpowell-VkGw2YyLwTCa/A/roGhosh2...@public.gmane.org> writes:
> Now, if someone could just tell my why my with-flow stuff never
> renders without a reload... nunb had the same problem.

Just guessing, but I'd assume it's because the changes your with-flow is
making to the widget tree are too late to be seen.

Stephen Compall

unread,
Oct 16, 2008, 12:41:49 AM10/16/08
to webl...@googlegroups.com
Robin Lee Powell <rlpowell-VkGw2YyLwTCa/A/roGhosh2...@public.gmane.org> writes:
> For the record, what I had assumed from Slava's example is that
> there was book-keeping in place to make with-flow a one-time thing;
> that is, once a widget was replaced by with-flow, the system would
> remember that it had been replaced and show the new widget forever
> until answer was called.
>
> That seems more intuitive to me; is it feasible/worth implementing?

Besides what Ian already mentioned, I think it would elevate with-flow
to first-class status, possibly precluding alternative flow mechanisms.
What I mean by it not being first-class is that with-flow can be
implemented with the `continuation' slot on widgets, that being a
function, CPS transformation of the contents, and nothing else.

For example, I have a `with-grow-flow' that will be contrib'd with more
testing later that behaves like this:

(defmacro with-grow-flow (composite &body body)
"With each invocation of `yield' in BODY, append the widget given to
COMPOSITE, save a copy of COMPOSITE's subwidget list, save the
continuation, and reset COMPOSITE's widgets to the saved value."

so `answer'ing on a widget multiple times will rewind the composite
to that state each time before resuming the continuation.

Reply all
Reply to author
Forward
0 new messages