Hi @cubuspl42, we don't have a document about how to implement an FRP system, so I'll try to give a sketch of it...
Yes, the topological sorting based on ranks is the most complex part, but the memory management is also complex in some implementations.
RANKING
The rule is that if node B depends on node A then A is executed first. We do this by 1. assigning ranks based on walking the directed graph and ensuring our invariant holds (and dealing with loops), and 2. putting every outstanding job into a priority queue and pulling them off the head, so we execute them in rank order.
This isn't the most efficient way of doing things, but it's easy to understand. A simple optimization with a huge effect is to have a shortcut when the queue contains 1 item. The most efficient implementation would maintain the execution order statically, but change it in response to the SWITCH primitive.
MEMORY MANAGEMENT
Objects are kept alive in the reverse of how they are in the observer pattern. So if B listens to A, then...
Observer pattern: B is kept alive by A
FRP: A is kept alive by B
This makes a lot of sense if you think of FRP as a way to solve all the problems with the observer pattern. The purpose of the observer pattern is to reverse dependencies, but it fails to do so for the memory dependency. So we fix that.
Since FRP uses the observer pattern under the covers, the easiest way to implement it is to deregister all listeners upon finalization of an FRP object. We also hold a reference to the object being listened to to establish the memory dependency in that direction.
In Javascript, weak references and finalizers are not possible. So, we use a reference counting system with the four-colour cycle collection algorithm you mentioned.
I haven't surveyed the FRP landscape for a while, so I can't tell you whether anyone else has documented this. Learning to implement this thing was a huge amount of work!
If you've got any other questions, ask away!