EventEmitter in OCaml

I want to implement node.js EventEmitter in OCaml, but I find I cannot to compare two functions when I implmenting emitter.on(eventName, listener) method. Any ideas?
When I compare two functions, error araise:

# let sum x = x + 1;;
# let add = sum;;
# sum = add;;
Exception: Invalid_argument "equal: functional value".

In this case probably physical equality is the way to go i.e. ==.

1 Like

== works,thanks.

let sum x =  x + 1
val sum : int -> int = <fun>
let add = sum
val sum : int -> int = <fun>

sum == add
bool = true

sum = add
Exception: Invalid_argument "compare: functional value".

It’s probably not the right thing to do still, if you write the same lambda capturing different things in its closure, it won’t compare equal… If you write the same lambda at different point in the program there’s a chance it’ll allocate a new distinct one…

let f = fun x -> x
let g = fun x -> x

let _ = assert (f != g)

I don’t think it is ever right to compare functions. what’s the nodejs implementation doing? trying to specify that first will help arise a proper solution in OCaml I imagine.

emitter.removeListener(eventName, listener) will find listener in the event list for the same event name in order to remove listener.

I just tried with nodejs and

let e = new EventListener;
e.on('error', () => {})
e.on('error', () => {})
e.removeListener('error', () => {})

doesn’t remove anything because the lambdas are distinct… would’ve been surprised if it did.

They really just designed an API like this. Why not ask the caller to pass a unique string tag for each listener, so that you can later correctly retrieve it…?

You are right, a unique tag is need to retrieve the listener.

In this case physical equality is appropriate because you are supposed to pass in a reference (a variable) to the event handler that you defined and set previously. When removing the handler, the handler is just removed from the array. The unique tag that you mentioned is just the memory location of the handler function. It should work fine as long as the same variable is used to both add and remove the handler.

I’m aware, this however is brittle and the manual makes no guarantees about memory locations of functions and their equality as far as I’m aware… We could have flambda be the default middle end few years down the road and it could mess with whatever assumptions you currently have…

But you don’t even have to go that far, just altering OP’s example a bit and using the builtin succ instead of writing sum reveals how easily this solution can break down… i.e. assert (succ != succ) passes.

Passing in a UID is something your API user completely controls on the other hand. My argument is that physical equality is never appropriate here, neither for OCaml nor JS.

Ah, good point. I didn’t realize that memory locations of named functions are unstable. In that case the best option is to return an opaque value from the on function, and use that to remove it e.g.

let evt_id = Emitter.on emitter "event-name" func
let () = Emitter.off emitter evt_id

Behind the scenes we can have a hash table of the event IDs mapping to the functions to run. The IDs can be generated randomly e.g. using the uuidm library.

3 Likes

That’s a good approach, yeah. Reduces API user’s burden (as well as flexibility/control e.g. to dynamically replace handlers by passing in the same UID).