"Reactive" Arrays and liftB

79 views
Skip to first unread message

Simon Van de Water

unread,
Dec 24, 2012, 12:11:06 PM12/24/12
to fla...@googlegroups.com
Hello everybody,

I am working on a project in FlapJax and I think I good make good use of "reactive arrays". I read through the docs, tutorial and demos and I haven't really found anything that fits my needs so I made something myself. I am working on a chat-application which has multiple chatrooms and I would like (for instance) the rooms to be kept in a "reactive Array" (such that the combobox that holds the list of rooms gets updated everytime a room is added/deleted). In the attachment you can find a working snippet but I will post the most important part here:
<script type = "text/javascript">
var i = 0;
function addEl(){
//first slot of array indicates whether element should be added or removed to the list. 0 indicates adding, 1 indicates removing.
roomsE.sendEvent([0,"room"+i]);
i = i + 1;
}
function removeEl(){
//first slot of array indicates whther element should be added or removed to the list. 0 indicates adding, 1 indicates removing.
roomsE.sendEvent([1,document.getElementById("index").value]);//remove element at index supplied by the input-field "index".
}

function updateRooms(a) {
$('#cboRooms').empty();
for(j = 0; j < a.length; j++) {
$('#cboRooms')[0].options.add(new Option(a[j]));
};
  }

var roomsE = receiverE();
//decide whether to remove or add item inside combiner-function based on first slot of array.
var roomsCollectE = roomsE.collectE([],function(newEl,existingEls) { 
if (newEl[0] == 0) {
return [newEl[1]].concat(existingEls); 
} else
existingEls.splice(newEl[1],1);
return existingEls;
});

var roomsB = startsWith(roomsCollectE,[]);
roomsB.liftB(updateRooms);
</script>

This works, but I think I'm making things way too complicated. Is there an easier (and thus better) way to do this?
Another remark/question I have is this: whenever eventSourceE.liftB(f) is evaluated, the function 'f' I pass to it will be evaluated already. Is there some way to enforce that 'f' gets evaluated for the first time whenever I explicitly send an event to eventSourceE instead of when liftB is evaluated? I tried with the function skipFirstE() but I didn't find a solution to this.
If you need more information or if my question is unclear, please let me know!

Thank you,
Simon

testingArray.html
Message has been deleted
Message has been deleted
Message has been deleted

Leo Meyerovich

unread,
Dec 26, 2012, 4:24:10 PM12/26/12
to fla...@googlegroups.com
Hi Simon,

Before, I tried something similar. Array manipulation functions were wrapped to send updates to functions over arrays, and I also played with a more incremental version that would track changes to individual array elements. The latter was important for incremental optimization, but seemed too hard to use. 

The important parts seems to be a) consistent maintenance of identity via detecting *all* array manipulations (arr[i] = v, arr.push(v), ..) and b) controlling when imperative reactions should occur (bundling a transaction).

Essentially, I can imagine supporting interactions like

var arr = [1,2,3];
var arrB = arrayObserverB(rawA);
var lenB = arrB.liftB(count);

arrB.push(1); //raw arr gets an extra entry and lenB increases in length

arr.push(2); //raw arr gets an extra entry, but lenB has not increased
fx.update(arr); //lenB and any other dependent behaviors update

var arr2 = arrB.identity; //get underlying array
arr2.push(2); //arbitrar imperative manipulations
fx.update(arr2) //batch update


I suspect the new proxy and weak map APIs would be helpful: 

-- "fx.update" would be implemented in a space-efficient way via "weak maps" (https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/WeakMap)


In a similar spirit to "arrayObserverB", we can probably add "objectObserverB()". 


Happy holidays,

- Leo



On Dec 26, 2012, at 7:46 AM, Simon Van de Water <simon.va...@gmail.com> wrote:

If there are other newbies like me who found this question useful; this is a less complex approach: 

<script type = "text/javascript">
var arrays = new Object();
arrays.rooms = [];
var i = 0;
function addEl(){
roomsE.sendEvent(arrays.rooms.concat(["room" + i]));
i = i + 1;
}
function removeEl(){
var index = document.getElementById("index").value;
var arr = arrays.rooms;
arr.splice(index,1);
roomsE.sendEvent(arr);
}
function updateRooms(a) {
alert("SHIT SON");
$('#cboRooms').empty();
for(j = 0; j < a.length; j++) {
$('#cboRooms')[0].options.add(new Option(a[j]));
};
  }
var roomsE = receiverE();
var roomsB = startsWith(roomsE,[]);
insertValueE(roomsE,arrays,"rooms");
roomsB.liftB(updateRooms);
</script>

--
Flapjax home page: www.flapjax-lang.org
Flapjax list: groups.google.com/group/flapjax
Post: fla...@googlegroups.com
Unsubscribe: flapjax-u...@googlegroups.com

Leo Meyerovich

unread,
Dec 26, 2012, 9:21:09 PM12/26/12
to fla...@googlegroups.com
I just shared a JSFiddle of reactive arrays: http://jsfiddle.net/lmeyerov/v9SRR/ .  Note that it uses the experimental ECMAScript feature of "direct proxies." Firefox supports proxies, but I think Chrome has a slightly older version so it doesn't work.

The test suite shows some fun interactions:

var arr = ['init'];
var p = wrap(arr);

p.push('a'); //pushes into arr and notifies anything dependent upon p
arr.push('b'); //pushes into arr, but does not notify p dependents
p[20] = 10; //sets arr and notifies
arr[21] = 12; //sets arr but does not notify
p.liftB(function(arr) { console.log(slice(arr,0)); }); //show snapshot of arr whenever it changes

Note that I didn't do any special casing for 'push'; any array mutation events should also be captured :)

Regards,

Simon Van de Water

unread,
Dec 26, 2012, 10:03:20 PM12/26/12
to fla...@googlegroups.com
Hello Leo,

Thank you for your reaction. I think I might use your approach later on in my project but for now (the rooms) I managed to use this simple code. It's not really reactive but it does what I need for now ...:
<script type = "text/javascript">
var arrays = new Object();
arrays.rooms = [];
var i = 0;
function addEl(){
roomsE.sendEvent(arrays.rooms.concat(["room" + i]));
i = i + 1;
}
function removeEl(){
var index = document.getElementById("index").value;
var arr = arrays.rooms;
arr.splice(index,1);
roomsE.sendEvent(arr);
}
function updateRooms(a) {
$('#cboRooms').empty();
for(j = 0; j < a.length; j++) {
$('#cboRooms')[0].options.add(new Option(a[j]));
};
  }
var roomsE = receiverE();
var roomsB = startsWith(roomsE,[]);
insertValueE(roomsE,arrays,"rooms");
roomsB.liftB(updateRooms);
</script>

I am stuck now however with something else while I was using this approach. 
I wanted to use insertValueE for "custom objects" I made in JS but this doesn't seem to work.
I am making a list of elements and the elements are  lists of 2 strings themselves. This list of 2 strings represents a "room" with a name and a password. Like this: [["room1","password1"],["room2,"password2"]]; 
If I however try to insert such a nested list into a field using the insertValueE() function, an error is called: Uncaught TypeError: Cannot read property '0' of undefined
Because this didn't work, I tried send an event with a room-object (which is an object I made that consists out of 2 fields) and if I try to do this, no error is thrown, but the object also isn't inserted in the field I passed to the insertValueE()-function. Here is the code snippet I am talking about:
function Room(roomName,password) { //maybe put this in seperate script and load it into client as well as server.
console.log("CREATING ROOM: " + roomName + password);
this.roomName = roomName;
this.password = password;
}
var r = new Room("testname","pw");
var testE = receiverE();
insertValueE(testE,arrays,"roomz");
//testE.sendEvent(["testNormalString"]); //works perfectly!
//testE.sendEvent(["roomName","password"]);//doesn't work. Get this JS error: Uncaught TypeError: Cannot read property '0' of undefined
testE.sendEvent(r);//result: arrays.roomz = []; (no object is added to "arrays.roomz")

Can anybody tell me why 'r' isn't inside arrays.roomz after running this code? It doesn't give me any JS-errors. If I should create a new discussion for this; Please tell me.

Happy Holidays,
Simon

Leo Meyerovich

unread,
Dec 26, 2012, 11:07:19 PM12/26/12
to fla...@googlegroups.com
Simon, I am struggling to understand your message.

Maybe you can post a jsfiddle?

Regards,

- Leo

Leo Meyerovich

unread,
Dec 27, 2012, 7:19:40 PM12/27/12
to fla...@googlegroups.com
I updated the jsfiddle a bit to better work with the Behavior inheritance chain and described how to use it @ http://lmeyerov.blogspot.com/2012/12/mixing-functionalimperative-reactive.html .

One issue with this approach is that the reactive proxy has "Array" in the prototype. An alternative choice is "Behavior".  This is a question of reflecting identity (proxying Array and Behavior methods such as  liftB and concat already work).  The question is which identity is more important: sharing with non-FRP code and having an Array prototype (e.g., proxies instanceof/typeof), or sharing with FRP code and having a Behavior prototype. Currently, by prioritizing the former, "fxArr.liftB(…)" will work while "liftB(fxArr, …)" wouldn't. A similar approach to the current one could set Behavior as the prototype just as easily.

I see two solutions to enabling both usage modes:

1) Keep Array as the prototype and swap out the many "instanceof Behavior" checks in fx to be more structural/duck typed

2) Define the "getPrototypeOf" proxy trap to return Behavior or Array based on whether the activation record is fx internals or user code. This solution might end up being similar to 1) .

Thoughts?

- Leo

Simon Van de Water

unread,
Dec 29, 2012, 9:22:47 AM12/29/12
to fla...@googlegroups.com
Dear Leo, 

You shouldn't worry too much about the last question I posted. It had nothing to do with the reason why I opened this discussion and it probably has more to do with the fact that I'm just not that familiar yet with Flapjax.
The jsfiddle you provided supports way more functionality than what I needed so I am very thankful for that and I am sure I can use it as I advance through the world of flapjax (and reactive programming).
As I am fairly new to Flapjax I think I am not skilled enough to give any valuable feedback on how you should proceed to enable both usage modes. I apologize for that...

Greetings,
Simon

Shriram Krishnamurthi

unread,
Dec 29, 2012, 9:38:39 AM12/29/12
to fla...@googlegroups.com
These are great questions, and useful to all readers. So don't
apologize -- keep the questions coming!
Reply all
Reply to author
Forward
0 new messages