Journal entry for
I now have an implementation in three ways and I’m not sure which is best. All this was because fixIO is not lazy enough to do:
fixIO $ \unsubscribe -> do
subscribe e $ \propagate -> do
...
unsubscribe
When originally implementing subscribe as:
subscribe :: Event a -> (Maybe a -> IO ()) -> (Unsubscribe, Maybe (Maybe a))
This fixIO problem didn’t exist because processing the initial value happened after the subscribe had run.
Now I was trying to implement the FRP interface without having to return this Maybe (Maybe a)
(occurrence known at subscribe time or not and what’s its value).
Maybe with my new insights I’ll give the original version a shot again and see if I can make a shorter implementation using it.
The three options I came up with are:
- branch no-unsubscribe-queue-via-passing-in-unsubscribe-to-subscribe
- Here I implemented event subscription without returning the unsubscribe, instead it’s passed in.
type Subscriber a = Unsubscribe -> Maybe a -> IO () type Unsubscribe = IO () newtype Event Impl a = Event { subscribe :: Subscriber a -> IO () }
I had to use
unsafePerformIO
to save the unsubscribe action in some places. The advantage of this one is that I didn’t have to use a delaying queue for unsubscribing incoincidence
, but everything else became a little harder. - branch no-unsubscribe-queue-via-passing-in-unsubscribe-to-subscribe
- Here I have subscribe as:
type Subscriber a = Maybe a -> IO () newtype Event Impl a = Event { subscribe :: Subscriber a -> IO Unsubscribe }
Here
coincidence
looks like:coincidence :: Event Impl (Event Impl a) -> Event Impl a coincidence coincidenceParent = cacheEvent $ Event $ \propagate -> do subscribe coincidenceParent $ do maybe (propagate Nothing) $ \e -> do innerEUnsubscribeRef :: IORef (IO ()) <- newIORef $ pure () occWasKnownRef <- newIORef False unsubscribe <- subscribe e (\occ -> do writeIORef occWasKnownRef True join (readIORef innerEUnsubscribeRef) propagate occ) writeIORef innerEUnsubscribeRef unsubscribe occKnown <- readIORef occWasKnownRef when occKnown unsubscribe
Which mimics the original subscribe.
Every other function is simpler to implement.
- branch coincidence-using-toClearQueue
type Subscriber a = Maybe a -> IO () newtype Event Impl a = Event { subscribe :: Subscriber a -> IO Unsubscribe }
This one uses the event occurrence clearing queue to unsubscribe from the inner coincidence event.coincidence :: Event Impl (Event Impl a) -> Event Impl a coincidence e = cacheEvent $ Event $ \propagate -> do subscribe e $ maybe (propagate Nothing) (addToQueue toClearQueueRef <=< (`subscribe` propagate))
I’m going to add the rest of the Reflex semantics test suite (skipped fan/Dynamic stuff) for some added confidence and then go back to the original subscribe function definition to see if I can make things a little shorter with it.
The original subscribe function as found in Reflex is here https://github.com/parenthetical/frp-journey/tree/original-subscribe-definition.