Showing posts with label Purescript. Show all posts
Showing posts with label Purescript. Show all posts

Sunday, July 29, 2018

Using Signals as input to Halogen

As I mentioned in my last post, Signals are very easy to use with Pux because its entire architecture is built around them. However this is not the case with Halogen and I have been investigating the very latest (as yet unreleased) code from its GitHub master repository to discover what needs to be done in order to recognize signals - in my case of MIDI device connections/disconnections and MIDI events.

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 mempty
where emit has the signature:
 
   emit :: forall m a. Emitter m a -> (Unit -> a) -> m Unit
We 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 next
and 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.

Tuesday, October 24, 2017

How to Initialise PureScript Pux

Pux is an implementation in PureScript of the Elm architecture, vintage 0.17. Elm 0.17 was built around FRP and you can see this carried over into Pux - for example in the type of the main event loop:
  
    foldp :: ∀ fx. Event -> State -> EffModel State Event fx
If you remember this version of Elm, you will recognise this as classic FRP - foldp folds from the past to the present and produces a result. As with Elm, this is a combination of a new model (State) coupled with the possibility of issuing asynchronous effects. These event streams are thus time-ordered and are modelled as Signals. One thing that is nice about PureScript is that there is no runtime and hence no magic about how the event streams are produced and consumed. Bodil Stokke's excellent Signal library has attempted, wherever possible, to try to maintain API equivalence with Elm. It has very clear semantics for signal manipulation.

On the whole, Pux is very well documented with good coverage of how user interactions generate DOM events which are then fed back into the model with foldp. What is not covered is how you can initialise it with values from elsewhere. This post intends to shed some light on initialisation techniques. There are three main cases to consider. You may need to pass a simple data structure to the model, you may need to kick off an initialisation event inside Pux or you may need to feed in signals from some external source.

Initialisation with Static Data

In some cases, you may need to run some effect inside the Eff monad to grab some resource such as a database connection. I will take my examples from audio - what I want to do is to discover if the browser supports web-midi. This can be achieved in JavaScript by means of a call to requestMIDIAccess. This is an effectful computation and so it must be wrapped in the Eff monad in order to make it available to PureScript:
  
   -- | WEBMIDI Effect
   foreign import data WEBMIDI :: Effect

   foreign import webMidiConnect
     :: ∀  eff. (Eff (wm :: WEBMIDI | eff) Boolean)

All you now need to do is run this initialisation code before starting Pux and you can initialise the model by passing it the connection state:
  
   main = do

     webMidiConnected <- webMidiConnect

     app <- start
       { initialState: initialState webMidiConnected
       , view
       , foldp
       , inputs: []
       }

     renderToDOM "#app" app.markup app.input

An Initialisation Event

Another thing you may wish to do is to make sure an Event runs as soon as Pux is started. The key insight here is that inputs are of type Signal and you can manufacture a Signal for a single initialisation event using the constant combinator. So, to carry on with the audio theme, your Event type might include an instruction to load a soundfont for a particular musical instrument and you want to start off with a grand piano. So, your Event ADT might look like this:
  
   data Event
      = NoOp 
      | RequestLoadFont InstrumentName
      | DeviceConnection Device
      | MidiMessage MidiEvent
      | ...
what you need to do is to create a Signal from the RequestLoadFont event and feed it to the inputs:
  
   initFont :: Signal Event
   initFont = constant $ RequestLoadFont AcousticGrandPiano

   main = do
  
   pp <- start
       { initialState: initialState
       , view
       , foldp
       , inputs: [initFont]
       }

Signals

Lastly, you may have entire message streams that you'd like to incorporate, Typically, these might emanate from JavaScript - for example the web-midi API can give you streams of connection/disconnection messages as devices such as MIDI keyboards attach and detach and streams of MIDI events such as NoteOn when keys are pressed. Again, these can be modelled in PureScript with Eff - this time by incorporating a callback function:
  
   -- | detect any input device as it connects or disconnects
   foreign import detectInputDevices :: ∀ e. (Device -> Eff e Unit) -> Eff e Unit

   -- | listen to web-MIDI event messages
   foreign import listen :: ∀  e. (MidiEvent -> Eff e Unit) -> Eff e Unit
These effects can be built up into Signals and from there into Channels. I show the code here for devices, but that for MIDI events is essentially identical:
  
   initialDeviceSignal :: Signal Device
   initialDeviceSignal =
     constant initialDevice

   deviceSignal :: Device -> Signal Device
   deviceSignal d =
     foldp (flip const) d initialDeviceSignal

   deviceChannel :: ∀ eff. Eff (channel :: CHANNEL | eff) (Channel Device)
   deviceChannel = 
     channel initialDevice

   sendDevice :: ∀ eff. Channel Device -> Device -> Eff (channel :: CHANNEL | eff) Unit
   sendDevice chan d =
     send chan d

   -- | create a channel for MIDI device connections/disconnections and feed it from web-midi
   createDeviceChannel :: ∀ eff.
     Eff
       ( channel :: CHANNEL
       | eff
       )
       (Channel Device)
   createDeviceChannel = do
     channel <- deviceChannel
     _ <- detectInputDevices (sendDevice channel)
     pure channel

Now, to initialise Pux, all you need to do is to subscribe to these channels, each of which gives you a Signal, which you then map to the corresponding Events and then feed in to the inputs.
 
   main = do

    webMidiConnected <- webMidiConnect

    deviceChannel <- createDeviceChannel
    eventChannel <- createEventChannel
    let
      deviceSubscription = subscribe deviceChannel
      eventSubscription = subscribe eventChannel
      deviceSignal = map DeviceConnection deviceSubscription
      eventSignal = map MidiMessage eventSubscription

    app <- start
       { initialState: initialState webMidiConnected
       , view
       , foldp
       , inputs: [ deviceSignal, eventSignal ]
       }

    renderToDOM "#app" app.markup app.input

Elm 0.18

So, what about Elm 0.18 and its ports? This is now irrelevant. In PureScript, you have no restriction in wrapping JavaScript functions and so there is no necessity at all for anything analogous to a port. You can either use Eff, as shown above, to establish your initialisation patterns or you can use asynchronous effects (Aff) inside the Pux application proper whenever you need to call JavaScript asynchronously.

Many thanks to Justin Woo for pointing me towards creating signals from Eff.