Search code examples
coroutinepurescript

How do I join multiple coroutine Consumers with a single Producer in PureScript?


I'm think I'm starting to understand Coroutine producers and consumers, but I'm having the hardest time putting consumers and produces together in useful ways. If I have this producer of APIEvent...

type ID = String

data APIEvent
  = Connecting
  | Fail String
  | Success String

derive instance gAPIEvent :: Generic APIEvent
instance showPeerEvent :: Show APIEvent where show = gShow

getID :: Producer APIEvent (Aff (avar :: AVAR, console :: CONSOLE, random :: RANDOM)) (Maybe ID)
getID = produceAff (\emit -> do
  i <- liftEff $ randomInt 0 10
  if i < 9
    then do
      -- If producer was called with `loop`, the
      -- producer will restart.
      emit $ Left $ Fail "Failure to get id."
      emit $ Right Nothing
    else do
      emit $ Left $ Success "id"
      emit $ Right $ Just "id"
  )

How would I connect it to two consumers that do system logging and basic output, e.g.

logFailures :: Consumer APIEvent (Aff (avar :: AVAR, console :: CONSOLE, random :: RANDOM)) ID
logFailures = forever $ do
  event <- await
  lift $ do
    case event of
      (Fail message) -> log $ "LOG: " <> message

showOutput :: Consumer APIEvent (Aff (avar :: AVAR, console :: CONSOLE, random :: RANDOM)) ID
showOutput = forever $ do
  event <- await
  lift $ do
    log $ "STDOUT: " <> (show event)

I've tried using joinConsumers, but that creates a consumer of (Tuple APIEvent APIEvent) instead of just APIEvents, so then it doesn't type check.

main = launchAff $ do
  v <- runProcess $
        (joinConsumers logFailures showOutput)
        `pullFrom` (loop getPeerID)
  log $ "Process result: " <> (show v)

Edit:

This is what I ended up with.

main = launchAff $ do
  v <- runProcess $
    transformConsumer
      (forever $ transform (\i -> Tuple i i))
      (joinConsumers logFailures showOutput)
    `pullFrom` (loop getID)
  log $ "Process result: " <> (show v)

Outputs...

LOG: Failure to get id.
STDOUT: Test.SO.Fail "Failure to get id."
LOG: Failure to get id.
STDOUT: Test.SO.Fail "Failure to get id."
STDOUT: Test.SO.Success "id"
Process result: "id"

Solution

  • As you've noticed, you can use joinConsumers to combine two Consumers into a Consumer of Tuples. So now you just need a way to change a Consumer of Tuple a a into a regular Consumer of a.

    You can do this by using the transformConsumer function, which transforms a Consumer using a Transformer:

    transformConsumer 
      :: forall i o f m a
       . (MonadRec m, Parallel f m)
      => Transformer i o m a
      -> Consumer o m a
      -> Consumer i m a
    

    But how to create the Transformer there? Well, you can create one from a regular function using the transform function:

    transform
      :: forall m i o
       . Monad m
      => (i -> o)
      -> Transformer i o m Unit
    

    The function type you need is a -> Tuple a a, so \a -> Tuple a a will do the job.