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.