RE: [Flapjax: 653] Node.js adaptation - EventEmitters pass multiple arguments

168 views
Skip to first unread message

Arjun Guha

unread,
Jan 18, 2011, 1:24:02 PM1/18/11
to michaelsbradleyjr, Flapjax
Flapjax has 1-arity event streams only by convention. There is no
technical reason it cannot support n-ary event steams as you suggest.
However, you might avoid n-ary streams because would require n-ary
event transformers--if you ever mix them up, JavaScript will silently
ignore the arity-mismatch error. What do you think? Flqpjax is
unfortunately latently typed.

Arjun From: michaelsbradleyjr
Sent: Monday, January 17, 2011 20:02
To: Flapjax
Subject: [Flapjax: 653] Node.js adaptation - EventEmitters pass
multiple arguments
I've submitted this same inquiry as in issue for the FlaPjax GitHub
repo:
https://github.com/brownplt/flapjax/issues/#issue/2

I thought it might be a good idea to post it in this group too.

-------

I've been experimenting with usage of flapjax in the context of
node.js:
https://github.com/ry/node

This involves changing the local copy of flapjax-2.1.js so that
references to `addEventListener` -> `addListener` and
`removeEventListener` -> `removeListener`, per the node.js API:
http://nodejs.org/docs/v0.3.4/api/

Also node.js scripts don't have a `window` object by default but a
stand-in can easily be created: `var window = {}'. Then the global
setTimeout can be referenced to to the window object:
`window.setTimeout = setTimeout`.

With those changes in place, flapjax works just fine inside a node.js
script:


require('./flapjax-2.1.js')
var myReceiver = receiverE()
...


One thing I have discovered is that while instances of the node.js
EventEmitter class (http://nodejs.org/docs/v0.3.4/api/
events.html#events.EventEmitter) can emit an arbitrary number of
arguments (i.e. objects) to a callback function, the `$E` function in
flapjax will pass only one of the emitted objects.

So for example:


require('./flapjax-2.1.js')
var events = require('events')

var myEmitter = new events.EventEmitter()
var myE = $E(myEmitter, 'eventA')
myE.mapE(function (arg1, arg2) { console.log(arg1, arg2) })

myEmitter.emit('eventA', '1', '2')


If I save that script to `myScript.js` and run it from bash with `node
myScript.js` the the following is printed to the terminal: `1
undefined`.


In other words, `$E` is effectively ignoring the second argument-
object being emitted on `eventA`.

Now, I can "fix" it in the following manner:


...

var myR = receiverE()
myEmitter.on('eventA', function (arg1, arg2) {
myR.sendEvent({ arg1 : arg1, arg2 : arg2 })
})

myR.mapE(function (evt) { console.log(evt.arg1, evt.arg2) })
myEmitter.emit('eventA', '1', '2')

When I run it this time I get what I wanted: `1 2` is printed to the
terminal.

However, this workaround introduces additional complexity into the
program. Certainly, in larger scripts, once the proper receivers have
been setup and the emitters "remapped" to `$E`, `mapE`, etc., one
still benefits from the clarity that flapjax brings to reasoning about
the evented dataflow.

I'm curious though how hard it would be to adapt flapjax so that it
can pass an arbitrary number of emitted objects from an EventEmitter
instead of one (the first one) only. I spent an hour+ poking around
in the source, looking for some candidate function that I could modify
to do just this, but I came up empty handed (so far).

Your thoughts on this would most appreciated.

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

michaelsbradleyjr

unread,
Jan 18, 2011, 8:50:13 PM1/18/11
to Flapjax
If I'm writing my own instances of node.js's EventEmitter class, I can
ensure that their emitted events correspond to 1-arity listeners, i.e.
if I need to pass multiple arguments I can wrap them inside an array
([arg1,arg2]) or hash ({arg1:arg1, arg2:arg2}). Then in my mapE()
expressions I can explicitly refer to those arguments.

However many of the emitters that are native to the node.js API, like
its http.Server, and emitters that are part of libraries built for
node.js ( https://github.com/ry/node/wiki/modules ) are not setup that
way. Hence, if I I wish to use them unmodified with Flapjax, I have
to "remap" their emitted events as I described in my initial post, by
way of a receiverE().

You're correct that n-ary streams and listeners are prone to arity-
mismatch. It's a known "issue" for node.js, but it's dealt with
primarily by way of adhering to community-accepted conventions when
building instances of EventEmitter (and documenting one's emitters!)
-- you can get a feel for that when reading through the node.js API.

So, despite the potential for mismatch, having n-ary event stream
support in Flapjax would be a real boon. If you can give me some
direction on where / how I can add support for this within the Flapjax
library, I will be glad to attempt this myself. I will admit that I
have no formal background in computer science, but I'm a quick learner
and always up for a challenge. I've worked through the whole
flapjax-2.1.js file, so I'm familiar with what it contains, though I
don't claim to have a deep understanding yet of its inner workings.
Note that I'm using Flapjax as a library, I'm not using the compiler.

A personal goal of mine is to "master" Flapjax's internals, and too
Michael Greenberg's lens.js and Nicokaly Platonov's Joose's internals
( https://github.com/Joose/Joose ). Presently, I'm more of a consumer
of those libraries but certainly wish to ramp-up to a deeper
understanding. My hope is to build an open source node.js-powered web-
app framework (with REST compliance at heart), wielding those
libraries and a few others, like jsdom.

My initial work is here: https://github.com/michaelsbradleyjr/Smoothie

My ping-pong.js program works (in "experiments" directory), and with a
single process (one core), I've been able to push through upwards of
50 mbps and thousands of reqs/sec. There is a memory leak, though,
and I'm sure it has to do with how I'm constructing the listeners
($E() expressions). node.js keeps track of the listeners registered
for any emitter, and I may need to resort to cleaning up some of my
listeners manually (if that is in fact what's causing the "non-
releasing" time-linear growth in memory consumption).

I'm new to the functional style and my instincts lead me to
constructing the logic in an imperative style. I'm trying to think
more in functional terms, though for what I have in mind, I think it
won't be possible or desirable to avoid imperative logic altogether.

Thanks for your input, and thanks for writing Flapjax. It's surely
one of the most interesting and promising javascript libraries I've
ever encountered.




On Jan 18, 12:24 pm, Arjun Guha <arjun.g...@gmail.com> wrote:
> Flapjax has 1-arity event streams only by convention. There is no
> technical reason it cannot support n-ary event steams as you suggest.
> However, you might avoid n-ary streams because would require n-ary
> event transformers--if you ever mix them up, JavaScript will silently
> ignore the arity-mismatch error. What do you think? Flqpjax is
> unfortunately latently typed.
>
> Arjun From: michaelsbradleyjr
> Sent: Monday, January 17, 2011 20:02
> To: Flapjax
> Subject: [Flapjax: 653] Node.js adaptation - EventEmitters pass
> multiple arguments
> I've submitted this same inquiry as in issue for the FlaPjax GitHub
> repo:https://github.com/brownplt/flapjax/issues/#issue/2
>
> I thought it might be a good idea to post it in this group too.
>
> <snip>

Arjun Guha

unread,
Jan 19, 2011, 12:50:49 AM1/19/11
to michaelsbradleyjr, Flapjax
Understood. Would you also need a convention for returning multiple
values from an event stream transformer? If not, only the "first layer"
of transformers may be n-ary. From: michaelsbradleyjr
Sent: Wednesday, January 19, 2011 7:20
To: Flapjax
Subject: [Flapjax: 655] Re: Node.js adaptation - EventEmitters pass

--

michaelsbradleyjr

unread,
Jan 19, 2011, 2:02:10 AM1/19/11
to Flapjax
I'm inclined to to think that only the "first layer" needs to support
n-ary event streams/emitters.

But, could you construct a pseudo-code example of what it might look
like if an event stream transformer were able to return multiple
values? I'm trying to picture that in my head and am not sure I have
the right idea.

Thanks.



On Jan 18, 11:50 pm, Arjun Guha <arjun.g...@gmail.com> wrote:
> Understood. Would you also need a convention for returning multiple
> values from an event stream transformer? If not, only the "first layer"
> of transformers may be n-ary.
>
>
> From: michaelsbradleyjr
> Sent: Wednesday, January 19, 2011 7:20
> To: Flapjax
> Subject: [Flapjax: 655] Re: Node.js adaptation - EventEmitters pass
> multiple arguments
> If I'm writing my own instances of node.js's EventEmitter class, I can
> ensure that their emitted events correspond to 1-arity listeners, i.e.
> if I need to pass multiple arguments I can wrap them inside an array
> ([arg1,arg2]) or hash ({arg1:arg1, arg2:arg2}).  Then in my mapE()
> expressions I can explicitly refer to those arguments.
>
> However many of the emitters that are native to the node.js API, like
> its http.Server, and emitters that are part of libraries built for
> node.js (https://github.com/ry/node/wiki/modules) are not setup that
> way.  Hence, if I I wish to use them unmodified with Flapjax, I have
> to "remap" their emitted events as I described in my initial post, by
> way of a receiverE().
>
> <snip>

Arjun Guha

unread,
Jan 24, 2011, 3:06:53 PM1/24/11
to fla...@googlegroups.com
But, could you construct a pseudo-code example of what it might look
like if an event stream transformer were able to return multiple
values?

Flapjax has a mapE function.  Imagine adding mapEMultiple that expects the transformer function to return an array of values:

evtStream.mapEMultiple(function(evt) {
  return [ val1, val2 ];
}); 

However, I don't think you need this.  If all you care about is getting the "first layer" to work, how about the following untested patch to flapjax-impl.js.  It's a simple hack to allow pulses to carry multiple values.  Of course, it could be cleaner, but I wasn't to affect as few LOC as possible:

diff --git a/fx/flapjax-impl.js b/fx/flapjax-impl.js
index 0cb45e4..7a8c4b0 100644
--- a/fx/flapjax-impl.js
+++ b/fx/flapjax-impl.js
@@ -156,12 +156,13 @@ var foldR = function (fn, init /* arrays */) {
 var doNotPropagate = { };
 
 //Pulse: Stamp * Path * Obj
-var Pulse = function (stamp, value) {
+var Pulse = function (stamp, value, moreValues) {
   // Timestamps are used by liftB (and ifE).  Since liftB may receive multiple
   // update signals in the same run of the evaluator, it only propagates the 
   // signal if it has a new stamp.
   this.stamp = stamp;
   this.value = value;
+  this.moreValues = moreValues; // may be undefined
 };
 
 
@@ -400,13 +401,21 @@ Behavior.prototype = new Object();
 
 var receiverE = function() {
   var evt = internalE();
-  evt.sendEvent = function(value) {
-    propagatePulse(new Pulse(nextStamp(), value),evt);
+  evt.sendEvent = function() {
+    if (arguments.length <= 1) {
+      propagatePulse(new Pulse(nextStamp(), arguments[0]),evt);
+    }
+    else {
+      propagatePulse(new Pulse(nextStamp(), arguments[0], slice(arguments, 1)),
+                     evt);
+    }
   };
   return evt;
 };
 
 
+
+
 //note that this creates a new timestamp and new event queue
 var sendEvent = function (node, value) {
   if (!(node instanceof EventStream)) { throw 'sendEvent: expected Event as first arg'; } //SAFETY
@@ -452,7 +461,12 @@ EventStream.prototype.mapE = function(f) {
   };
   
   return createNode([this],function(pulse) {
-    pulse.value = f(pulse.value);
+    if (typeof pulse.moreValues === 'undefined') {
+      pulse.value = f(pulse.value);
+    }
+    else {
+      pulse.value = f.apply(this, ([pulse.value]).concat(pulse.moreValues));
+    }
     return pulse;
   });
 };

Reply all
Reply to author
Forward
0 new messages