17

I read Deprecating the Observer Pattern with Scala.React and found reactive programming very interesting.

But there is a point I can't figure out: the author described the signals as the nodes in a DAG(Directed acyclic graph). Then what if you have two signals(or event sources, or models, w/e) depending on each other? i.e. the 'two-way binding', like a model and a view in web front-end programming.

Sometimes it's just inevitable because the user can change view, and the back-end(asynchronous request, for example) can change model, and you hope the other side to reflect the change immediately.

Lai Yu-Hsuan
  • 25,828
  • 23
  • 91
  • 156
  • Is your question specific to scala.react? Last I recall it wasn't that usable – nafg Jan 30 '14 at 02:27
  • @dcsbral / OP can you please clarify? – nafg Jan 31 '14 at 07:30
  • @nafg You misspelled my nick, so I didn't get to see your comment. I expect the question to be specific to scala.react indeed. Though I'd love your input into how your own framework deal with it. – Daniel C. Sobral Feb 05 '14 at 18:09
  • I have to say, I'm really disappointed with the answers. They all seem to go talk about how other people have solved it in other FRP software, or tell you to go read someone else's paper which talk about the subject and draw your own answers from it. That's not how Stack Overflow answers should be. I awarded the bounty to the only answer that actually speaks of a solution, though it (the answer) is a kind of cop out. – Daniel C. Sobral Feb 05 '14 at 18:13
  • @DanielC.Sobral - sorry about that. In reactive (at least currently) it's not hard since it's not based on an explicit graph. You can just update e.g. a Var that the signal depends on. Also there's .distinct and .nonrecursive to prevent infinite loops. In the future though G-d willing I would hope to have a way to use it graph based. – nafg Feb 07 '14 at 03:07
  • The question is not directly about Scala.React, any solution about reactive programming is welcomed. By the way, I found a [blog post](http://baconjs.blogspot.tw/2013/02/chicken-egg-and-baconjs.html) explaining how Bacon.js handles this problem. – Lai Yu-Hsuan Feb 10 '14 at 09:38

4 Answers4

3

The loop dependencies in a reactive programming language can be handled with a variety of semantics. The one that appears to have been chosen in scala.React is that of synchronous reactive languages and specifically that of Esterel. You can have a good explanation of this semantics and its alternatives in the paper "The synchronous languages 12 years later" by Benveniste, A. ; Caspi, P. ; Edwards, S.A. ; Halbwachs, N. ; Le Guernic, P. ; de Simone, R. and available at http://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=1173191&tag=1 or http://virtualhost.cs.columbia.edu/~sedwards/papers/benveniste2003synchronous.pdf.

2

After scanning the paper, I can't find where they mention that it must be acyclic. There's nothing stopping you from creating cyclic graphs in dataflow/reactive programming. Acyclic graphs only allow you to create Pipeline Dataflow (e.g. Unix command line pipes).

Feedback and cycles are a very powerful mechanism in dataflow. Without them you are restricted to the types of programs you can create. Take a look at Flow-Based Programming - Loop-Type Networks.


Edit after second post by pagoda_5b

One statement in the paper made me take notice...

For correctly ordered graphs, this process monotonically proceeds to greater levels, thus ensuring data consistency, i.e., the absence of glitches.

To me that says that loops are not allowed within the Scala.React framework. A cycle between two nodes would seem to cause the system to continually try to raise the level of both nodes forever.

But that doesn't mean that you have to encode the loops within their framework. It could be possible to have have one path from the item you want to observe and then another, separate, path back to the GUI.

To me, it always seems that too much emphasis is placed on a programming system completing and giving one answer. Loops make it difficult to determine when to terminate. Libraries that use the term "reactive" tend to subscribe to this thought process. But that is just a result of the Von Neumann architecture of computers... a focus of solving an equation and returning the answer. Libraries that shy away from loops seem to be worried about program termination.

Dataflow doesn't require a program to have one right answer or ever terminate. The answer is the answer at this moment of time due to the inputs at this moment. Feedback and loops are expected if not required. A dataflow system is basically just a big loop that constantly passes data between nodes. To terminate it, you just stop it.

Dataflow doesn't have to be so complicated. It is just a very different way to think about programming. I suggest you look at J. Paul Morison's book "Flow Based Programming" for a field tested version of dataflow or my book (once it's done).

Matt Carkci
  • 191
  • 5
2

Replying @Matt Carkci here, because a comment wouldn't suffice

In the paper section 7.1 Change Propagation you have

Our change propagation implementation uses a push-based approach based on a topologically ordered dependency graph. When a propagation turn starts, the propagator puts all nodes that have been invalidated since the last turn into a priority queue which is sorted according to the topological order, briefly level, of the nodes. The propagator dequeues the node on the lowest level and validates it, potentially changing its state and putting its dependent nodes, which are on greater levels, on the queue. The propagator repeats this step until the queue is empty, always keeping track of the current level, which becomes important for level mismatches below. For correctly ordered graphs, this process monotonically proceeds to greater levels, thus ensuring data consistency, i.e., the absence of glitches.

and later at section 7.6 Level Mismatch

We therefore need to prepare for an opaque node n to access another node that is on a higher topological level. Every node that is read from during n’s evaluation, first checks whether the current propagation level which is maintained by the propagator is greater than the node’s level. If it is, it proceed as usual, otherwise it throws a level mismatch exception containing a reference to itself, which is caught only in the main propagation loop. The propagator then hoists n by first changing its level to a level above the node which threw the exception, reinserting n into the propagation queue (since it’s level has changed) for later evaluation in the same turn and then transitively hoisting all of n’s dependents.

While there's no mention about any topological constraint (cyclic vs acyclic), something is not clear. (at least to me)

First arises the question of how is the topological order defined.

And then the implementation suggests that mutually dependent nodes would loop forever in the evaluation through the exception mechanism explained above.

What do you think?

pagoda_5b
  • 7,007
  • 1
  • 24
  • 37
  • 1
    http://en.wikipedia.org/wiki/Topological_sorting -- "A topological ordering is possible if and only if the graph has no directed cycles, that is, if it is a directed acyclic graph (DAG)." – Boyd Stephen Smith Jr. Jan 29 '14 at 22:59
2

Check your MVC knowledge. The view doesn't update the model, so it won't send signals to it. The controller updates the model. For a C/F converter, you would have two controllers (one for the F control, on for the C control). Both controllers would send signals to a single model (which stores the only real temperature, Kelvin, in a lossless format). The model sends signals to two separate views (one for C view, one for F view). No cycles.

Based on the answer from @pagoda_5b, I'd say that you are likely allowed to have cycles (7.6 should handle it, at the cost of performance) but you must guarantee that there is no infinite regress. For example, you could have the controllers also receive signals from the model, as long as you guaranteed that receipt of said signal never caused a signal to be sent back to the model.

I think the above is a good description, but it uses the word "signal" in a non-FRP style. "Signals" in the above are really messages. If the description in 7.1 is correct and complete, loops in the signal graph would always cause infinite regress as processing the dependents of a node would cause the node to be processed and vice-versa, ad inf.

As @Matt Carkci said, there are FRP frameworks that allow loops, at least to a limited extent. They will either not be push-based, use non-strictness in interesting ways, enforce monotonicity, or introduce "artificial" delays so that when the signal graph is expanded on the temporal dimension (turning it into a value graph) the cycles disappear.