milibopp / carboxyl

Functional Reactive Programming library for Rust
Mozilla Public License 2.0
405 stars 19 forks source link

Cyclic SignalMut definition #89

Closed potocpav closed 9 years ago

potocpav commented 9 years ago

Is it possible to implement a function analogous to Signal::cyclic for SignalMut? Alternatively, is it possible to define SignalMut recursively using today's API?

milibopp commented 9 years ago

Interesting question… Thanks for bringing it up.

First of all, note that SignalMut is only intended as a workaround for cases, when types from other libraries do not allow immutable updates, or a performance optimization for large data structures contained in a signal.

Now, if you look at the current implementation of SignalMut, it is a very thin interface over a Signal<ReadOnly<A>>, where ReadOnly<A> is just a more restricted version of Arc<RwLock<A>>. So I could imagine that it is possible to define this in terms of the current API.

That said, SignalMut breaks the invariance implicitly expected by Carboxyl due to the inner mutability of the Arc<RwLock<A>>. It tries to provide a semantically consistent interface on top of this violation though. sample is simply possible that way and switch simply does not make sense performance-wise (and this whole type is only a performance optimization).

Regarding cyclic, I am somewhat unsure. Admittedly it seems useful, but also difficult. The transactional semantics dictate that sampling a signal during a transaction should always yield the value it had before the transaction. This is relevant for switch and cyclic and requires a signal to hold on to both the previous and the new value during transaction. For SignalMut this may be problematic in principle, as you want to update the values in place.

For this to make sense performance-wise, it may be necessary to employ some sort of double-buffering mechanism instead, which would not allow us to re-use Signal for SignalMut any more. But note that this strategy would require either cloning the whole thing or executing the update function twice once per transaction. And that may already render the optimization useless. Thus, it should probably be yet another alternative signal type.

In conclusion, I am not sure how to approach this. Nonetheless, if you feel like giving it a try, I'll happily answer questions about the codebase.

potocpav commented 9 years ago

I did not expect the problem to run so deep. I hoped that I could just use carboxyl as a blackbox, stick some SignalMuts in there, and be done with it :) I am building a reactive UI library (for robotics visualization and control, based on glium).Everything time-dependent is a reactive primitive. I thought that I can't force users to have all state Clone, hence the SignalMut. Already, my test widget has a big image inside.

Now I realized that I am already impure in Signals, because the objects have a mpsc::Sender to send OpenGL commands to the main thread (OGL needs to be single-threaded). Does it break anything, even if there is no way to observe the main thread?

Based on the options you described for implementing cyclic for SignalMut I don't think double-buffering is useful for me, and I can't imagine how updating twice would work?. If I just use Signal and Arc<RwLock<A>> wherever needed, how bad would that break semantics?

milibopp commented 9 years ago

The bottom line is, that there's no way to tell how bad it would be. SignalMut hides this internal mutability as much as possible, so that a user cannot exploit it to break the semantics. But if you do use Arc<RwLock<A>> you may and up storing the result somewhere well after it is valid. That way you loose out on the advantage provided by FRP, that you don't have to reason about these details any more.

As a general architectural suggestion, I would recommend you to look into how e.g. Elm or Cycle.js deal with it, as they have collected some practical experience for larger applications. That's for the web, but the basic idea should be the same everywhere. Basically you have drivers for effectful external APIs (such as HTTP, OpenGL, a browser's DOM…) that consume the streams/signals produced by your application imperatively and feed you back their inputs. That's where you do the cycling. That way you may be able to avoid some usages of explicit cycles.

I intended SignalMut mostly for large pieces of model data, which cannot feasibly be cloned on each update. A good example of this would be the state of a physics engine. It is not meant for dealing with actual I/O side effects and I would not recommend using it for that purpose to be honest.

PS: please do keep me updated about your progress, it really sounds like an interesting project. ;)

potocpav commented 9 years ago

Yep, I was just browsing the web for how people do that. Did not look into web libraries yet, good idea. I hope that I don't get stuck in the middle of the way and just rewrite everything in a non-FRP way (which is more familiar to me).

I will try to keep you updated, thanks for your help :)

milibopp commented 9 years ago

Yes, FRP appears to have more traction in frontend than in desktop apps.

Anyway, I'm glad I could help. All the best!