
Heinrich Apfelmus
So, netwire is essentially based on the abstraction
type Stoplight red green = Behavior' (Either red green)
of time-varying values that can either be a "normal" value of type green , or an "exceptional" ("inhibiting") value of type red . (I have dropped the input dependence, so that arrow composition becomes normal function application.)
Here, Behavior' denotes a type of continues time-varying values, with the small twist that it can "detect some flanks", namely when the value jumps from Right to Left . In this way, the flank can be interpreted as an Event in the reactive-banana sense.
This is not really how it works. Inhibition causes the rest of the wire to inhibit, in other words: _ . empty = empty
The applicative instance is the composition of the applicative instances for Behavior' and Either
instance Monoid red => Applicative (Stoplight red) where pure x = pure (Right x) f <*> x = (<$)> <$> f <*> x
if we assume that Either has the following applicative instance
instance Monoid red => Applicative (Either red) where pure = Right (Right x) <*> (Right y) = Right (x y) (Left x) <*> (Right y) = Left x (Right x) <*> (Left y) = Left y (Left x) <*> (Left y) = Left (x <*> y)
In particular the above means that with (<*>) when the function wire inhibits, the application itself inhibits: empty <*> _ = empty The Monoid constraint is on the Alternative class instead, where selection happens. The reason why it has to be a monoid is for the Alternative laws to hold.
What I particularly like about this approach is the following: I like to think of Behavior as continuous functions, not necessarily because they are really continuous, but because this means that the semantics of Behavior is independent of any "update frequency", which is key point in simplifying code with FRP. Unfortunately, this means that we cannot "detect flanks", because a continuously changing Behavior simply does not change in discrete steps.
The Stoplight type solves this problem by introducing an explicit "this is a flank" value, i.e. by using the fact that the only way an Either type can change is in discrete steps. Due to parametricity, I can't put this information into the general Behavior type, but the special case Behavior (Either red green) can make use of this.
Netwire doesn't do everything to hide the discrete nature of stepping, but this isn't really a conceptual choice, but rather the simple fact that I provide predefined wires that expose this (for example in Control.Wire.Prefab.Accum). If you use only time-continuous wires, then the perception is the same as in a continuous model, probably even more so as handling events becomes simple algebra instead of explicit switching: "yes" . holdFor 1 (periodically 2) <|> "no" The reason is that Netwire is developed with flexibility in mind. In fact the original motivation was to enable development of reactive networking applications, where time or continuity isn't much of a concern. I wanted to be able to express such systems declaratively without giving up the ability to reason easily about how it will behave at runtime. There is an experimental web framework based on Netwire 1 called Webwire, and I'm planning to revive it, because FRP makes developing web applications (server side!) really convenient, especially when you have to handle forms or other interactive things.
On the other hand, while the combination of Behaviors and Events allows for some slick switching combinators, I'm not entirely sure whether the mixture of two unrelated concepts (continuous functions vs singular occurrences) is too much of a conceptual burden.
The wire concept is at least equivalent to the classic events/behaviors split, because you can translate both behaviors and events to wires. You will find that most event signal functions from Yampa are also present in Netwire, except that they are potentially inhibiting wires instead of event signals. Greets, Ertugrul -- Not to be or to be and (not to be or to be and (not to be or to be and (not to be or to be and ... that is the list monad.