foldp :: ∀ fx. Event -> State -> EffModel State Event fxIf 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 UnitThese 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 channelNow, 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.