The state of a Halogen system progresses by means of a series of actions that are defined in our query algebra. Usually these actions emanate from Dom events but what we want to achieve is for our Signal to generate the actions instead. Our query algebra might contain the following:
data Query a = Init a | HandleDeviceConnection Device a | HandleMidiEvent Midi.TimedEvent a | ..and we want to be able to generate the HandleDeviceConnection and HandleMidiEvent actions.
Creeating EventSources
Instead of a Signal, Halogen uses an EventSource which is defined as follows:newtype EventSource m a = EventSource (m { producer :: CR.Producer a m Unit, finalizer :: Finalizer m })
This operates as an effect within some monad 'm' and which emits actions of type 'a'. When it is run, it returns a producer coroutine (from package purescript-couroutines) that emits these actions and the EventSource itself runs in the same monad `m`. The other part of the definition is a Finalizer which performs some sort of cleanup if the EventSource needs to be torn down after use. In our case, we don't need to do anything and so we can just use mempty (it has a Monoid instance).
In fact you are discouraged from constructing an EventSource yourself in favour of using one of the constructor functions that Halogen provides - effectEventSource, affEventSource, or eventListenerEventSource which will build one for you if you are able to provide a suitable callback function. When our Signal is run, it does so in the Effect monad and so we choose the first of these. It requires a callback of the following type where the type variable 'a' is intended to stand for our query algebra:
(Emitter Effect a -> Effect (Finalizer Effect))and so, for our Device Signal, we can simply run the signal and map it on to the required action using the emit function that Halogen provides:
adaptDeviceSignal :: Signal Device -> (Emitter Effect (Query Unit) -> Effect (Finalizer Effect)) adaptDeviceSignal sig = do \emitter -> do let getNext device = do emit emitter (HandleDeviceConnection device) runSignal $ sig ~> getNext pure memptywhere emit has the signature:
emit :: forall m a. Emitter m a -> (Unit -> a) -> m UnitWe can, of course, do an entirely equivalent thing for the MIDI Event Signals with a midiEventSignal function.
Subscribing to the EventSource
Finally, we need to set up the subscription to our Event streams when the program initialises (i.e. in response to the Init action). For example:eval (Init next) = do -- subscribe to device connections and disconnections deviceSource <- H.liftEffect $ effectEventSource (adaptDeviceSignal deviceSignal) _ <- H.subscribe deviceSource -- subscribe to MIDI event messages from these devices midiSource <- H.liftEffect $ effectEventSource (adaptMidiEventSignal midiEventSignal) _ <- H.subscribe midiSource pure nextand we're done. Many thanks to Nicholas Scheel for pointing me towards EventSource and to the fact that it was changing in the forthcoming Halogen release.