baconjs / bacon.js

Functional reactive programming library for TypeScript and JavaScript

Home Page:https://baconjs.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Solving cyclic dependencies

Cleric-K opened this issue · comments

This is probably not exactly an issue with bacon itself but since I'm new to FRP I would like to hear opinions.

For the project I'm working on I came to a stage where I need a kind of feedback mechanism. Since I still think mostly in imperative programming terms, I can't figure out a way to avoid it.

Basically I have a simple Requests stream that has to be transformed into Responses stream. This is a node.js project and the requests are actually send over a serial port, thus requests have to be send synchronously. So far I have used .flatMapConcat() which did great in queuing the requests and dispatching them one by one.
But for some technical reasons I have to avoid the queue. I'm not going into details but in short, in specific situations the requests have to be dispatched either immediately or not at all (let's say that executing the queued requests later is of no use).

Of course the logical thing is to use .flatMapFirst() which does exactly the above. My problems started when the requirement emerged that the dropped requests have to be redirected to another stream - for simplicity, let's say to a logger.

I created a property which indicates whether there's an active request and tried to modulate the request stream with it. Here's a simplified diagram:
image

The problem with this approach is that it introduces a cyclic dependency. When I try to run this I get stack overflow:

RangeError: Maximum call stack size exceeded
    at flushDepsOf (...\node_modules\baconjs\dist\Bacon.js:457:21)
    at flushDepsOf (...\node_modules\baconjs\dist\Bacon.js:463:9)
    at flushDepsOf (...\node_modules\baconjs\dist\Bacon.js:463:9)

I found a way around this by breaking the dependency chain by separating the flow graph with a Bus():
image

This works as expected since busy no longer seems to be dependent on Requests. But it's little ugly of course.

I looked into the code related to flushDepsOf() and the problem is that the flushed map gets filled in for a given observable only when all it's dependencies are flushed. Of course if the same observable is found somewhere within these dependencies it becomes infinite recursion.

I tried moving the flushed[obs.id] = true; line before iterating through the dependencies. This of course avoids the infinite recursion but probably can cause troubles in some cases.

This is my first ever work with FRP so I don't really know what I'm up against here.

First: is cyclic dependence really a sign of wrong approach to the problem? Feedback mechanisms exist in all domains of engineering, programming, etc, so probably it should be allowed in FRP too. If it is not, is there some trick that can rearrange the flow in a such a way that cycles can be avoided (so far I haven't thought of one)?

Second: if cyclic dependencies are not strictly forbidden, does this mean that bacon can be improved to better handle them (like the solution with moving the flushed line above)?

Thank you!