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 in coincidence, 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 }
       coincidence :: Event Impl (Event Impl a) -> Event Impl a
       coincidence e = cacheEvent $ Event $ \propagate -> do
         subscribe e $ maybe (propagate Nothing) (addToQueue toClearQueueRef <=< (`subscribe` propagate))
This one uses the event occurrence clearing queue to unsubscribe from the inner coincidence event.

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.