Hi
> However, unless you have a strong reason to use arrowized FRP I would recommend that you go with one of the first-class FRP libraries.
TL;DR: Shameless self promotion ahead: we built an elementary library that seems to subsume many others, including AFRP and Classic FRP libraries, I'd like to know how it compares.
Seeing what's just been said about netwire, I'd like to ask how these compare to each other. Among themselves, and also in relation to a separate construct that Manuel Bärenz and I built (note: I am the Yampa maintainer; Yampa is alive and well and more updates are coming your way :) ).
In 2016 we published an article [1; mirror: 4] and a library [2] which aim at merging ideas in this field. I always thought they were pretty powerful, and so far I haven't found many limitations. (But I am biased, so maybe not the ideal judge.)
It combines the CPS-based arrowized construct of Yampa with a monad, in a tiny definition:
newtype MSF m a b = MSF { step :: a -> m (b, MSF m a b) }
So, you provide one input sample and get, in a monadic context, an output and a continuation. Next time you provide the next input to the continuation, and so on.
You can define stream as:
type MStream m b = MSF m () b
You can define sinks as:
type MSink m a = MSF m a ()
They have really cool properties [3], for instance, they are arrows, and if the monad is commutative then the arrow is commutative. We have instances for many other Arrow* classes.
You can also define FRP on top of it, in the time-continuous sense, by using a Reader monad:
type YampaSF a b = MSF (Reader Time) a b
We have a version of Yampa defined on top of this that runs full (free and commercial) games just fine. It's API compatible (for what it implements).
And, you can define classic FRP signals (and sinks, a-la reactive banana and, if paired, more similar to Daniel Winograd-Cort's work or Keera Hails):
type Signal a = MStream Time a
So you can use applicative style:
s :: Signal Double
s = -- predefined somewhere
biggerS :: Signal Double
biggerS = (* 100) <*> s
You can do extremely cool things just by altering the monad:
- If the monad is Maybe, they terminate because there may not be a continuation (and an output).
- If the monad is Either, they terminate with a result. This is the basis for switching, which we get "for free".
- If the monad is [], they spawn. This implements parallelism with broadcasting for free.
- You can use a Writer monad and some smart tricks to do continuous collision detection.
- You can use state if to avoid the bottleneck issue that people criticise AFRP for.
- You can use transformers to stack these effects.
- You can also use IO as your monad, if you want to access mouse position and other external stuff, print a log, or sink directly from your network.
So far, I've found that we can pretty much do anything we want with this. It's simple to use, classic or arrowized at will (you can combine the two). I'm investigating performance, which for the games I've tried is really good and gives us flat and low memory profiles, and I believe we can do some pretty smart things with GADTS and re-writes to make things as fast as they can theoretically be.
How does this compare to other FRP and F;RP libraries around?
Cheers
Ivan