fmapFunc and order of operations

65 views
Skip to first unread message

Kris Nuttycombe

unread,
Jan 25, 2010, 12:38:12 PM1/25/10
to liftweb
Hi, all,

I'm in the process of trying to create my own custom form elements
that combine other, existing types of elements. Here's the element
that's currently giving me fits:

def optionalDateField(value: DateTime, func: Option[DateTime] =>
Any, attrs: (String, String)*): ChoiceHolder[NodeSeq] = {
var useDate = false
var curDate: Option[DateTime] = None
fmapFunc(SFuncHolder(s => {Log.debug("Got " + s + " for
optional field"); if (s == "Some") func(curDate)})) {
name => {
val noneItem = ChoiceItem[NodeSeq](Text("None"),
<input type="radio" name={name} value="None" checked="true"/>)
val someItem = ChoiceItem[NodeSeq](dateField(value, d
=> {Log.debug("Got " + d + " as submitted date."); curDate =
Some(d)}), <input type="radio" name={name} value="Some"/>)
ChoiceHolder(noneItem :: someItem :: Nil)
}
}
}

dateField is implemented as:

def dateField(value: DateTime, func: DateTime => Any, attrs:
(String, String)*): Elem = {
var curDate = value.withMillisOfDay(0)

val daySelectId = randomString(8)
def dayOptions = (1 to
curDate.property(dayOfMonth).getMaximumValue).map(i => (i.toString,
i.toString)).toList
def daySelect = untrustedSelect(
dayOptions,
Full(curDate.getDayOfMonth.toString),
d => curDate = curDate.withDayOfMonth(d.toInt),
"id" -> daySelectId
)

def monthSelect = ajaxSelect(
(1 to 12) map (i => (i.toString, i.toString)),
Full(curDate.getMonthOfYear.toString),
{m =>
curDate = curDate.withMonthOfYear(m.toInt)
ReplaceOptions(daySelectId, dayOptions,
Full(curDate.getDayOfMonth.toString))
}
)

def yearSelect = ajaxSelect(
(curDate.getYear to curDate.getYear + 10) map (y =>
(y.toString, y.toString)),
Full(curDate.getYear.toString),
{y =>
curDate = curDate.withYear(y.toInt)
ReplaceOptions(daySelectId, dayOptions,
Full(curDate.getDayOfMonth.toString))
}
)

def hourSelect = select(
(0 to 23) map (h => (h.toString, h.toString)),
Full(curDate.getHourOfDay.toString),
h => curDate = curDate.withHourOfDay(h.toInt)
)

def minuteSelect = select(
(0 to 59) map (h => (h.toString, h.toString)),
Full(curDate.getMinuteOfHour.toString),
m => curDate = curDate.withMinuteOfHour(m.toInt)
)

def hiddenField = hidden(() => func(curDate))

attrs.foldLeft(
<span>
{yearSelect} / {monthSelect} / {daySelect}
{hourSelect}:{minuteSelect}
{hiddenField}
</span>
)(_ % _)
}

The problem that I'm running into is that because of having to collect
the state of various form elements in vars, I have no means to control
the order of operations. I'm attempting to use my optionalDateField in
the following snippet:

def addHold(xhtml: NodeSeq): NodeSeq = {
var start: Option[DateTime] = None
var end: Option[DateTime] = None

def onSubmit = for (d <- start; sub <- subscription) {
Log.debug("New hold submitted; end is " + end)
sub.addHold {
end match {
case Some(e) => SubscriptionHold.finite(sub, d, e,
Initiator.ADMINISTRATIVE)
case None => SubscriptionHold.infinite(sub, d,
Initiator.ADMINISTRATIVE)
}
}

sub.merge
EM.tx.commit
}

if (subscription.exists(_.isCurrentAsOf(new DateTime()))) {
bind("add_hold", xhtml,
"start_date" -> dateField(new DateTime, d => start = Some(d)),
"end_date" -> <span>{optionalDateField(new DateTime,
d => {Log.debug("Capturing end as " + d); end = d}).flatMap(i =>
i.xhtml ++ i.key)}</span>,
"submit" -> submit("Add Hold", onSubmit _)
)
} else {
NodeSeq.Empty
}
}

Here's a log trace showing the problem:

2010-01-25 17:26:58,502 DEBUG lift - Got Some for optional field
2010-01-25 17:26:58,505 DEBUG lift - Capturing end as None
2010-01-25 17:26:58,511 DEBUG lift - Got 2010-01-30T00:00:00.000Z as
submitted date.

How can I force the order of evaluation of the form elements such that
the submitted date variable will be set *before* the function passed
to fmapFunc is evaluated? This is the problem that spawned my "A
better approach to AJAX forms" thread. I've been beating my head
against this for days now, and I can't help but place the blame
squarely on the "pass a function that modifies a local var" pattern
that I complained about in the other thread. Does a better solution
exist? It just seems way too easy to shoot oneself in the foot with
this cruddy pattern.

Thanks,

Kris

Kris Nuttycombe

unread,
Jan 25, 2010, 1:04:36 PM1/25/10
to liftweb
I finally found a solution:

def optionalDateField(value: DateTime, func: Option[DateTime] =>
Any, attrs: (String, String)*): ChoiceHolder[NodeSeq] = {
var useDate = false
var curDate: Option[DateTime] = None

val df = dateField(value, d => {Log.debug("Got " + d + " as


submitted date."); curDate = Some(d)})
fmapFunc(SFuncHolder(s => {Log.debug("Got " + s + " for
optional field"); if (s == "Some") func(curDate)})) {
name => {
val noneItem = ChoiceItem[NodeSeq](Text("None"),
<input type="radio" name={name} value="None" checked="true"/>)

val someItem = ChoiceItem[NodeSeq](df, <input


type="radio" name={name} value="Some"/>)
ChoiceHolder(noneItem :: someItem :: Nil)
}
}
}

This causes the closures to be evaluated in the right order. So much
for idempotence.

Frustrated,

Kris

Marius

unread,
Jan 25, 2010, 1:07:02 PM1/25/10
to Lift
S.formFuncName .. should guarantee proper ordering of functions
application respecting the ordering of functions creation (and this is
used by fmapFunc).

I'm a bit tired to follow code from this page so could you please put
together a minimalistic application that I could just try?

Br's,
Marius

Kris Nuttycombe

unread,
Jan 25, 2010, 1:19:32 PM1/25/10
to lif...@googlegroups.com
On Mon, Jan 25, 2010 at 11:07 AM, Marius <marius...@gmail.com> wrote:
> S.formFuncName .. should guarantee proper ordering of functions
> application respecting the ordering of functions creation (and this is
> used by fmapFunc).
>
> I'm a bit tired to follow code from this page so could you please put
> together a minimalistic application that I could just try?
>
> Br's,
> Marius

Thanks, Marius, but I'm too time-crunched at the moment to boil this
down. In any case, I found a solution. My frustration is primarily
that ordering of function creation matters in the first place; making
the ordering of function creation less relevant is the point of my
proposal in the other thread. If the order that you do things in
matters, it completely hoses you for the purposes of composition (as I
painfully found out with this example.) Here, the composition is just
two calls deep and explicit, and even with only that small amount of
composition it was a pain to track down.

Does this make my proposal in the other thread make any more sense to you?

Kris

Marius

unread,
Jan 25, 2010, 1:38:21 PM1/25/10
to Lift

On Jan 25, 8:19 pm, Kris Nuttycombe <kris.nuttyco...@gmail.com> wrote:

> On Mon, Jan 25, 2010 at 11:07 AM, Marius <marius.dan...@gmail.com> wrote:
> > S.formFuncName .. should guarantee proper ordering of functions
> > application respecting the ordering of functions creation (and this is
> > used by fmapFunc).
>
> > I'm a bit tired to follow code from this page so could you please put
> > together a minimalistic application that I could just try?
>
> > Br's,
> > Marius
>
> Thanks, Marius, but I'm too time-crunched at the moment to boil this
> down. In any case, I found a solution. My frustration is primarily
> that ordering of function creation matters in the first place; making
> the ordering of function creation less relevant is the point of my
> proposal in the other thread. If the order that you do things in
> matters, it completely hoses you for the purposes of composition (as I
> painfully found out with this example.) Here, the composition is just
> two calls deep and explicit, and even with only that small amount of
> composition it was a pain to track down.

As I didn't look at your code in details I fail to see where ordering
hurts composition.

functions ordering matters because when submitting a form you'd want
functions to be called in the order of the fields "creation" ... not
randomly ... as HTTP parameters order is not guaranteed.

Perhaps we should add a generic mechanism to specify custom field
functions application order so that:

Assume having a a bind like:

bind("f", xhtml,
"field1" -> formOrder(0, SHtml.text("", f1)),
"field2 -> formOrder(2, "SHtml.text("", f2)),
"field3" -> formOrder(1, SHtml.text("", f3))
)

the functions evaluation on server side would be: f1, f3, f2.

Thoughts?

David Pollak

unread,
Jan 25, 2010, 1:40:56 PM1/25/10
to lif...@googlegroups.com

Ordering is well defined:
  • The order in which the functions are mapped
  • Skewed plus or minus based on value of S.formGroup
There's no way to avoid ordering.  The functions have to be executed in some order.  By default, the stuff in SHtml does this in the order the elements were defined, but sets S.formGroup to 1 for the submit button (so it's always the last function executed.)

More broadly, I think you might want to look at what I did with Screen and Wizard.  These are declarative mechanisms for defining forms, validations, and behaviors.  Where are these not working for you?
 

Kris

--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.




--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics

Kris Nuttycombe

unread,
Jan 25, 2010, 1:47:07 PM1/25/10
to lif...@googlegroups.com

The salient difference between what works and what doesn't from the
code I posted earlier is here (edited for readability):

http://paste.pocoo.org/show/169908/

My thought was that instead of explicitly specifying form element
closure evaluation order, you could instead have a FormElement[T]
class that would return a T on a method call.

Thus, it would be the user's code that would trigger the execution of
the closures, not the framework. The framework could do it in the
background to maintain backwards compatibilty, with lazy
initialization semantics for the stored value so that if the user's
code executed first, the closure would be executed and the result
stored (so that the framework wouldn't re-execute it.

Kris

>
>>
>> Does this make my proposal in the other thread make any more sense to you?
>>
>> Kris
>

Kris Nuttycombe

unread,
Jan 25, 2010, 1:51:02 PM1/25/10
to lif...@googlegroups.com

They have to be executed in some order; I just wish that the execution
could actually be performed by user code! That's the whole point of my
suggestion from the other thread.

> More broadly, I think you might want to look at what I did with Screen and
> Wizard.  These are declarative mechanisms for defining forms, validations,
> and behaviors.  Where are these not working for you?

I simply haven't had the time to port thousands of lines of form code
I've already written over to stuff on SNAPSHOT. I was supposed to have
this stuff released last Friday; my intention was to release on M8
since I haven't had time to test SNAPSHOT adequately because I've been
hunting down order of evaluation bugs.

Kris

Kris Nuttycombe

unread,
Jan 25, 2010, 2:09:48 PM1/25/10
to lif...@googlegroups.com
On Mon, Jan 25, 2010 at 11:51 AM, Kris Nuttycombe

More to the point, I think that with a slight modification in design,
I wouldn't *have* to think about order of evaluation. The fact that
it's exposed (and has been, for me, an infuriating source of bugs) is
a design smell.

Kris

David Pollak

unread,
Jan 25, 2010, 2:16:14 PM1/25/10
to lif...@googlegroups.com

But something's going to have to trigger the evaluation of the functions.  If everything is passive, then how does your "the form is submitted, now start pulling the rest of the form elements in" stuff work?

Since we moved to the S.formGroup stuff, other than this thread, there has not been an issue with form evaluation order, so tossing around "design smell" doesn't necessarily sit well.

Let's go back to what you're trying to accomplish.  I'm not understanding that at the top level (I haven't read through your Ajax posts, but will try to get to them today).  What's the delta between what you're trying to accomplish and the current state of the Screen/Wizard stuff?
 

Kris

--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

Kris Nuttycombe

unread,
Jan 25, 2010, 2:31:46 PM1/25/10
to lif...@googlegroups.com
On Mon, Jan 25, 2010 at 12:16 PM, David Pollak

I think what I'm trying to accomplish is at a somewhat lower level
than what Screen/Wizard is targeted at. The paste at
http://paste.pocoo.org/show/169908/ is an example of what I'm trying
to do, where order of operations has fouled me up. In the "before"
example, the function passed in gets evaluated with the correct value
from the radio button, but since the dateField was inline, it has not
yet been evaluated at the time of processing of the form that
contained it.

It just seems really bad to me that inlining something has different
behavior than does assigning it to a val and then using the val. This
is totally nonobvious to me, at least.

The essence of my suggestion from the other thread is this; get rid of
the var in the following snippet:

def mySnippet(xhtml: NodeSeq) {
var a: Option[String] = None

bind("a", xhtml,
"value" -> text("". s => a = Some(s)),
"submit" -> submit("Submit", () => a.foreach(doSomething _)
)
}

With my proposal, this would become:

def mySnippet(xhtml: NodeSeq) {
val aField = text("s", s => s)

bind("a", xhtml,
"value" -> aField,
"submit" -> submit("Submit", () => doSomething(aField!))
)
}

If you eliminate the var, you eliminate any questions with respect to
order of operations. I think that this is valuable with or without
your Screen/Wizard changes, unless the intent is to get rid of the
SHtml.* methods for form fields entirely.

Kris

David Pollak

unread,
Feb 15, 2010, 6:31:31 PM2/15/10
to lif...@googlegroups.com
If all the SHtml stuff returned a NodeSeq (or Elem) with AnswerHolder and AnswerHolder[T] looked like:

trait AnswerHolder[T] {
  def hasAnswer: Boolean
  def answer: Box[T]
  def map[S](f: T => S): Box[S]
  ...
}

Then we could get what you want (no explicit mutability) and keep APIs working the way they work now.  What do you think?

We could even introduce alternative SHtml stuff like:

def text(original: String): Elem with AnswerHolder[String]

What do you think?

Kris Nuttycombe

unread,
Feb 15, 2010, 7:06:10 PM2/15/10
to lif...@googlegroups.com
On Mon, Feb 15, 2010 at 4:31 PM, David Pollak
<feeder.of...@gmail.com> wrote:
> If all the SHtml stuff returned a NodeSeq (or Elem) with AnswerHolder and
> AnswerHolder[T] looked like:
>
> trait AnswerHolder[T] {
>   def hasAnswer: Boolean
>   def answer: Box[T]
>   def map[S](f: T => S): Box[S]
>   ...
> }
>
> Then we could get what you want (no explicit mutability) and keep APIs
> working the way they work now.  What do you think?
>
> We could even introduce alternative SHtml stuff like:
>
> def text(original: String): Elem with AnswerHolder[String]
>
> What do you think?

That looks good; I think it'd go a long way toward making the order of
operations a little more foolproof. The idea of 'Elem with
AnswerHolder' had never occurred to me; I guess I just always assumed
that Elem or Node was sealed, but it doesn't appear to be from the
scaladocs. That would certainly help on the backwards compatibility
front and would alleviate the need for the implicit conversion I'd
imagined from FormField[T] to NodeSeq.

What do you think about the notion of a "!" method that does the
unsafe answer.open_! ? I would think that in the vast majority of
cases, the Box being Empty would represent either a coding error or a
framework error. In the case of a coding error (the answer attempting
to be extracted during the initial rendering of the form elements)
this would fail fast, and in the case where the response is actually
being processed it should probably never be empty anyway.

Kris

David Pollak

unread,
Feb 15, 2010, 7:10:30 PM2/15/10
to lif...@googlegroups.com
On Mon, Feb 15, 2010 at 4:06 PM, Kris Nuttycombe <kris.nu...@gmail.com> wrote:
On Mon, Feb 15, 2010 at 4:31 PM, David Pollak
> If all the SHtml stuff returned a NodeSeq (or Elem) with AnswerHolder and
> AnswerHolder[T] looked like:
>
> trait AnswerHolder[T] {
>   def hasAnswer: Boolean
>   def answer: Box[T]
>   def map[S](f: T => S): Box[S]
>   ...
> }
>
> Then we could get what you want (no explicit mutability) and keep APIs
> working the way they work now.  What do you think?
>
> We could even introduce alternative SHtml stuff like:
>
> def text(original: String): Elem with AnswerHolder[String]
>
> What do you think?

That looks good; I think it'd go a long way toward making the order of
operations a little more foolproof. The idea of 'Elem with
AnswerHolder' had never occurred to me; I guess I just always assumed
that Elem or Node was sealed, but it doesn't appear to be from the
scaladocs. That would certainly help on the backwards compatibility
front and would alleviate the need for the implicit conversion I'd
imagined from FormField[T] to NodeSeq.

What do you think about the notion of a "!" method that does the
unsafe answer.open_! ?

Sure.
 
I would think that in the vast majority of
cases, the Box being Empty would represent either a coding error or a
framework error. In the case of a coding error (the answer attempting
to be extracted during the initial rendering of the form elements)
this would fail fast, and in the case where the response is actually
being processed it should probably never be empty anyway.

Can you open a ticket and assign it to me and mark it for M4 referencing this thread?
 

Naftoli Gugenheim

unread,
Feb 15, 2010, 7:16:35 PM2/15/10
to liftweb
How would this be used?


2010/2/15 David Pollak <feeder.of...@gmail.com>

David Pollak

unread,
Feb 23, 2010, 6:23:56 PM2/23/10
to lif...@googlegroups.com
I started working on ticket #349 (https://liftweb.assembla.com/spaces/liftweb/tickets/349-make-shtml-%5Binput%5D-return-elem-with-answerholder%5Bt%5D ) and tried returning an Elem with Responder[T] (see http://lampsvn.epfl.ch/trac/scala/browser/scala/tags/R_2_7_7_final/src/library/scala/Responder.scala?view=markup ) and found that Elem (which subclasses Node which subclasses Seq[Node]) already has map/flatMap/foreach.  So... the API is not as clean as it could have been.

Do you have any thoughts on how to clean up the API to make it better?
Reply all
Reply to author
Forward
0 new messages