Open paf31 opened 6 years ago
The Rx* libraries solve this with the share
operator.
http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-share
A naive approximation of this is:
share :: forall a. Event a -> Effect (Event a)
share source = do
{ event: shared, push } <- create
_ <- subscribe source push
pure shared
Though Observable.share
has extra functionality like the shared stream automatically unsubscribing/resubscribing to the source stream based on the number of subscribers the shared stream has.
Update:
Testing with the code
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
import FRP.Event (Event, create, makeEvent, subscribe)
import FRP.Event.Class (fold)
main :: Effect Unit
main = do
{event: rootEvent, push} <- create
counter <- share $ fold (\_ x -> x + 1) rootEvent 0
_ <- subscribe counter (\x -> log ("Subscription 1: " <> show x))
log "Pushing first occurence"
push unit
_ <- subscribe counter (\x -> log ("Subscription 2: " <> show x))
log "Pushing second occurence"
push unit
share :: forall a. Event a -> Effect (Event a)
share source = do
{ event: shared, push } <- create
_ <- subscribe source push
pure shared
yields the result
Pushing first occurence
Subscription 1: 1
Pushing second occurence
Subscription 1: 2
Subscription 2: 2
Ah right, good point. This is probably worth adding as a combinator then, don't you think?
It's definitely a common use case.
Would a definition like
share :: forall a. Event a -> Effect { event :: Event a, unsubscribe :: Effect Unit }
share source = do
{ event, push } <- create
unsubscribe <- subscribe source push
pure { event, unsubscribe }
be sufficient? Or should it be something more complex and self-managing?
I've also got another combinator or two lying around, like merge
:
merge :: forall f a. Foldable f => NonEmpty f (Event a) -> Effect (Event a)
On a related note: do you see signals becoming a part of this library, or should they be separate?
newtype Signal a = Signal { event :: Event a, value :: Ref a }
create :: forall a. a -> Event a -> Effect (Signal a)
sample :: forall a. Signal a -> Effect a
transform :: forall a b. (a -> b) -> Signal a -> Effect (Signal b)
Sorry for the slow reply. Yes, I think share
would be a good function to add.
Why merge
and not oneOf
?
Do you have a use case in mind for signals? I think they should probably be kept separate.
merge
is what most "reactive" JS libraries call the function, and it's basically an effectful append
/fold
so it made sense. oneOf
implies something alternative-based doesn't it?
I started considering wrapping Event
to make Signal
when I was looking at adding offline functionality to an app. I wanted logic that would fire when the network status transitioned from online to offline and vice versa (no problem thanks to Event
), and components to receive the current status when they subscribe. A useful feature was also that I could also sample the current value at any time, for example when a user clicks a button, without needing to introduce a subscription and state somewhere.
Something like:
networkOnline :: Event Unit
networkOffline :: Event Unit
data NetworkStatus = Online | Offline
-- | The browsers current network status, with the initial value of Online.
networkStatus :: Signal NetworkStatus
networkStatus = unsafePerformEffect do
networkEvent <- Event.merge
[ const Online <$> networkOnline
, const Offline <$> networkOffline
]
Signal.create Online networkEvent
This is essentially an Event
-based version of Bodil's purescript-signals
library, except with no FFI and no implicit effects.
The above code would then give access to the following:
Signal.subscribe networkStatus \status -> ...
-- The current value value is sent to the subscriber immediately, and new values are sent
-- through as they come
Signal.sample networkStatus :: Effect NetworkStatus
-- Allows retrieving the current value from any effectful function
isOnline :: Signal Boolean
isOnline = unsafePerformEffect $ Signal.transform (_ == Online) networkStatus
-- Pointless type clobbering because I can't think of a good example for NetworkStatus
Okay, so I'm reading through the source of purescript-behaviors
[sic] and some of it looks familiar. Is there some way to get this Signal
functionality using behaviours?
I think having self managed version of share would be great!
From https://github.com/paf31/purescript-behaviors/issues/27: