robertdp / purescript-wire

Events and Signals for FRP. Monad instances included
12 stars 1 forks source link

Project objectives #11

Open paluh opened 4 years ago

paluh commented 4 years ago

Hi,

This library looks really interesting as it provides ready to use react-basic integration.

Would you be so kind and shortly describe what are the project objectives? Could you please tell me how the event implementation part differs from bodil/purescript-signal for example?

robertdp commented 4 years ago

Hi @paluh,

It's funny you bring up React integration, because I actually want to split that out into its own library so the core event and signal implementation can be used independently. This library started out just as an implementation for signals for reactive UI state modelling, because my main work project needed them.

I have looked at Bodil's library before, and didn't want to use it because of the impurity of the implementation in the FFI layer. It's modelled after part of the old Elm runtime, and would not work well with situations where you need the ability to dynamically manage your own subscriptions and transformations/compositions.

My original signal implementation was just a few helper functions using FRP.Event from purescript-event combined with a Ref for storing the most recent value. FRP.Event has a nice and simple implementation, and I had used it before server-side, but pretty quickly we ran into issues with how it handled unsubscribing with React. Fixing this required almost completely replacing the internal subscription logic, making me less happy about continuing to build on top of FRP.Event. And I wanted to experiment with FRP.

So for this library:

Wire.Event models events in a very similar way to FRP.Event with a few differences:

The React-specific modules came after examining the implementation of Recoil.js and realising that so much of their complex and messy implementation could be replaced with monadic signals. I'm still not sure of its usefulness though.

paluh commented 4 years ago

Hi @robertdp,

It's funny you bring up React integration, because I actually want to split that out into its own library so the core event and signal implementation can be used independently. This library started out just as an implementation for signals for reactive UI state modelling, because my main work project needed them.

To be honest I have just quickly noticed the quick differences like react integration so I wasn't sure if there are deeper motives behind this implementation - sorry. It seems that it is good idea to separate react pieces if the core stands on its own. This will clear the picture a bit too I think.

I have looked at Bodil's library before (...)

The rest of your response is just wonderful project statement which should go directly into the README I think :-) Anybody then can really appreciate your design choices and understand objectives and become a happy user of this lib! When the info is missing some people like me can think: "I'm afraid of the ecosystem fragmentation - is there any difference in this implementation or should I just stick to the current standard like bodil lib?".

By the way - I'm still trying to understand the details of the monad instances. From a distance I think that I understand that it is "something like ContT" - am I right? ;-)

robertdp commented 4 years ago

Not really like ContT, but I'll try to give an example of Event and Signal that will hopefully be intuitive.

Event:

data InputMethod = Controller | MouseAndKeyboard

data InputEvent
  = ControllerEvent Controller.Event
  | MouseEvent Mouse.Event
  | KeyboardEvent Keyboard.Event

switchInputMethod :: Event InputMethod

controllerEvents :: Event Controller.Event

mouseEvents :: Event Mouse.Event

keyboardEvents :: Event Keyboard.Event

inputMethod :: Event InputMethod
inputMethod = switchInputMethod <|> pure MouseAndKeyboard

getInputEvents :: InputMethod -> Event InputEvent
getInputEvents Controller = ControllerEvent <$> controllerEvents
getInputEvents MouseAndKeyboard = (MouseEvent <$> mouseEvents) <|> (KeyboardEvent <$> keyboardEvents)

inputEvents :: Event InputEvents
inputEvents = inputMethod >>= getInputEvents

Future events output by inputEvents will depend on the values that come through inputMethod. inputMethod simple combines the values emitted by switchInputMethod with an initial value of MouseAndKeyboard. Note that these values are being treated as individual, discreet events.

An important point is that all state in this example is subscriber-dependent. If switchInputMethod suddenly emits the value Controller, then it will update inputMethod and also inputEvents for any current subscribers. If a new subscriber joins after this, for that subscriber it will be as if the input method switch never happened and they will receive mouse and keyboard events.

Signal:

data InputMethod = Controller | MouseAndKeyboard

data InputState
  = ControllerState Controller.State
  | MouseAndKeyboardState Mouse.State Keyboard.State

inputMethod :: Signal InputMethod

controllerState :: Signal Controller.State

mouseState :: Signal Mouse.State

keyboardState :: Signal Keyboard.State

getInputState :: InputMethod -> Signal InputState
getInputState Controller = ControllerState <$> controllerState
getInputState MouseAndKeyboard = MouseAndKeyboardState <$> mouseState <*> keyboardState

inputState :: Signal InputState
inputState = inputMethod >>= getInputState

Signals have a value at all times, so they are continuous as opposed to events which are discreet. Because of this a default input method doesn't need to be provided, and the meaning and semantics of the data flow also changes.

Values are also no longer subscriber-dependent, because signals are inherently stateful. Any issues with subscription timing from the event-based example do not apply here.

robertdp commented 3 years ago

@paluh I finally got around to making a README.