Generalized tagRec?

8 views
Skip to first unread message

Artyom Shalkhakov

unread,
Jun 4, 2009, 7:05:16 AM6/4/09
to fla...@googlegroups.com
Hello,

I was writing a small file browser in Flapjax and have encountered
a problem with tagRec: there seems to be no way to make one
dynamically created tag to depend on another dynamically created
tag's events.

What I would like to achieve is a tree with branches that
can be shown or hidden.

The tree (or rather, a forest) for testing is the parse tree of an expression
a + b * c, which is translated to JS as follows:

> [{label: 'plus', children: [
> {label: 'a', children: []},
> {label: 'multiply', children: [
> {label: 'b',children: []},
> {label: 'c',children: []}]}]}]

Here's how it is converted to HTML:

> // fun :: Forest * Maybe Hash -> Dom
> function fun(y,vis) {
> return UL(vis || {}, map(function(x) {
> return tagRec(['click'],
> function(e) {
> var db = toggleE('block','none',e).startsWith('block');
> return LI(A({href:'#'+x.label},x.label),
> fun(x.children, {style: {display: db}}));
> });
> }, y));
> }

toggleE is defined as follows:

> function toggleE(a,b,e) {
> return e.collectE(true, function(_,y) {
> return !y;
> }).mapE(function(x) {return x? a : b;});
> }

The tree behaves unexpectedly, because tagRec reacts to *all*
events happening inside a LI, while it should only react to events
of A.

I think I can come up with a hack but would like to write in the
most obvious (and declarative :-)) way possible.

Could anybody help me, please?

Cheers,
Artyom Shalkhakov.

PS here's a demo: http://www.sound-city.kz/fj/tree.html

Artyom Shalkhakov

unread,
Jun 5, 2009, 7:53:27 AM6/5/09
to fla...@googlegroups.com
2009/6/4 Artyom Shalkhakov <artyom.s...@gmail.com>:

> I was writing a small file browser in Flapjax and have encountered
> a problem with tagRec: there seems to be no way to make one
> dynamically created tag to depend on another dynamically created
> tag's events.
>
> What I would like to achieve is a tree with branches that
> can be shown or hidden.
>
> The tree (or rather, a forest) for testing is the parse tree of an expression
> a + b * c, which is translated to JS as follows:
>
>> [{label: 'plus', children: [
>>       {label: 'a', children: []},
>>       {label: 'multiply', children: [
>>            {label: 'b',children: []},
>>            {label: 'c',children: []}]}]}]
>
> Here's how it is converted to HTML:
>
>> // fun :: Forest * Maybe Hash -> Dom
>> function fun(y,vis) {
>>     return UL(vis || {}, map(function(x) {
>>          return tagRec(['click'],
>>              function(e) {
>>                  var db = toggleE('block','none',e).startsWith('block');
>>                  return LI(A({href:'#'+x.label},x.label),
>>                                 fun(x.children, {style: {display: db}}));
>>              });
>>     }, y));
>> }

Suddenly I've found a solution which works okay and is still very nice.

Here it is:

> function fun(y,vis) {
> return UL(vis || {}, map(function(x) {

> var a = A({href:'#'+x.label},x.label);
> var db = toggleE('block','none',clicksE(a)).startsWith('block');
> return LI(a, fun(x.children,{style:{display:db}}));
> }, y));
> }

Strangely enough I got it working without any debugging at all, woot! :)

Cheers,
Artyom Shalkhakov.

Arjun Guha

unread,
Jun 5, 2009, 8:02:09 AM6/5/09
to fla...@googlegroups.com
Looking at your solution, you didn't actually need a generalized
tagRec since you had a 1-way dependency. However, there are cases
when you want a pair of tags to depend on events from each other. For
that, a generalized tagRec would be great.

Maybe one of the implementors will add it out soon.

Arjun

Jacob Baskin

unread,
Jun 5, 2009, 8:44:20 AM6/5/09
to fla...@googlegroups.com
I made a construct called rec_e for similar tasks in Continue/Resume:

rec_e :: (Event A -> Event A) -> Event A

It takes a function from events to events, and calls it in such a way that the
event stream it returns and the event stream it takes are "the same"
(naturally, they aren't actually the same, they just fire at the same time).
Then it also returns this event stream. It can be defined thus:

function rec_e(fn) {
var inE = receiver_e();
var outE = fn(inE);
outE.transform_e(function(_) {
inE.sendEvent(_);
});
return outE;

Artyom Shalkhakov

unread,
Jun 12, 2009, 6:16:28 AM6/12/09
to fla...@googlegroups.com
Hi,

Sorry for the delay.

2009/6/5 Arjun Guha <arjun...@gmail.com>:


> Looking at your solution, you didn't actually need a generalized
> tagRec since you had a 1-way dependency.  However, there are cases
> when you want a pair of tags to depend on events from each other.  For
> that, a generalized tagRec would be great.

I have found a simple example where you need a generalized tagRec.
(Actually, I need to solve this problem to continue with my current
project.)

Let's say we have two links (buttons, tabs...) "A" and "B" and we want
one of them to be "focused" (or "selected"). Focusing a link, as well as
"unfocusing" it, should change it's color accordingly.

If we had Scheme's letrec in JS, the solution would look as follows:

> letrec a = A({href:'#', style:{color:
> startsWith(mergeE(ev1.constantE('#777'),
> ev2.constantE('#000')),
> '#000')}}, "this is A")
> b = A({href:'#', style:{color:
> startsWith(mergeE(ev1.constantE('#000'),
> ev2.constantE('#777')),
> '#000')}}, "this is B")
> ev1 = clicksE(a)
> ev2 = clicksE(b)
> in UL(LI(a),LI(b))

How to do this in JS? The best solution I found involves tricky assignments
and effectful event streams.

I don't get how a menu (either hierarchical or not), a tabbed navigation,
etc. could be implemented without such cyclic dependencies. We could
do it imperatively, of course...

> Maybe one of the implementors will add it out soon.

Let's hope for the better then. :)

Cheers,
Artyom Shalkhakov.

Artyom Shalkhakov

unread,
Jun 12, 2009, 6:18:46 AM6/12/09
to fla...@googlegroups.com
Hi,

That's cool, but I couldn't adapt it to the problem at hand. Care
to elaborate?

Cheers,
Artyom Shalkhakov.

2009/6/5 Jacob Baskin <goin...@gmail.com>:

Jacob Baskin

unread,
Jun 12, 2009, 8:02:34 AM6/12/09
to fla...@googlegroups.com
On Friday 12 June 2009, Artyom Shalkhakov wrote:
> Hi,
>
> That's cool, but I couldn't adapt it to the problem at hand. Care
> to elaborate?

Sure. Rather than:

> letrec a = A({href:'#', style:{color:
> startsWith(mergeE(ev1.constantE('#777'),
> ev2.constantE('#000')),
> '#000')}}, "this is A")
> b = A({href:'#', style:{color:
> startsWith(mergeE(ev1.constantE('#000'),
> ev2.constantE('#777')),
> '#000')}}, "this is B")
> ev1 = clicksE(a)
> ev2 = clicksE(b)
> in UL(LI(a),LI(b))

Just do:

var list = null;

rec_e(function(clicks) {
var b_clicks = clicks.transform_e(
function(_) { return _ == '#000' ? '#777' : '#000'; });
var a = A({href:'#', style:{color: clicks.startsWith('#000')},
'this is A');
var b = A({href:'#', style:{color: b_clicks.startsWith('#000')},
'this is B');
list = UL(LI(a),LI(b));
return merge_e(clicksE(a).constant_e('#777'),
clicksE(b).constant_e('#000'));
});

The basic idea is that it's easy to write down the event stream you want at
the end of a function. We just need to get it back to the beginning of the
function and you can do anything you want. So that's what rec_e does--it takes
an event stream from the end of the function and feeds it back in at the
start.

Looking at this example, there's probably a more useful signature that would
let you return both the event stream you want to pass back and also another
return value (that becomes the return value of rec_e). That way, you could
just do

return [merge_e(clicksE(a).constant_e('#777'),
clicksE(b).constant_e('#000')),
UL(LI(a),LI(b))];

instead of doing the ugliness with the outer-scope variable to get the other
value out. This would just be:

function rec_e(fn) {
var inE = receiver_e();
var ret = fn(inE);
var outE = ret[0];
outE.transform_e(function(_) {
inE.sendEvent(_);
});
return ret[1];
}

And the signature would be:

rec_e :: (Event A -> [Event A, B]) -> B

Make sense?

Artyom Shalkhakov

unread,
Jun 13, 2009, 3:19:35 AM6/13/09
to fla...@googlegroups.com
2009/6/12 Jacob Baskin <goin...@gmail.com>:

> Just do:
>
> var list = null;
>
> rec_e(function(clicks) {
>  var b_clicks = clicks.transform_e(
>    function(_) { return _ == '#000' ? '#777' : '#000'; });
>  var a = A({href:'#', style:{color: clicks.startsWith('#000')},
>                   'this is A');
>  var b = A({href:'#', style:{color: b_clicks.startsWith('#000')},
>                   'this is B');
>  list = UL(LI(a),LI(b));
>  return merge_e(clicksE(a).constant_e('#777'),
>                            clicksE(b).constant_e('#000'));
> });

Simply great. :)

> Make sense?

Yes. Big thanks for the explanation.

Cheers,
Artyom Shalkhakov.

Reply all
Reply to author
Forward
0 new messages