Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project objectives #11

Open
paluh opened this issue Aug 20, 2020 · 4 comments
Open

Project objectives #11

paluh opened this issue Aug 20, 2020 · 4 comments

Comments

@paluh
Copy link

paluh commented Aug 20, 2020

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
Copy link
Owner

robertdp commented Aug 21, 2020

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:

  • Unsubscribing takes effect immediately
    If a subscriber unsubscribes from an FRP.Event after a new value has been emitted but before the subscriber has been notified, the subscriber will still be notified. If one of the subscribers is a React component, and the new value causes it to unmount a child component which is also a subscriber to the same event, the child is still notified after unmounting triggering a warning.
  • Monad instances
    FRP.Event has different goals based on how it is intended to be used. I wanted to experiment with more complex, sequential, stateful data flows for applications and so gave both Wire.Event and Wire.Signal monad instances.

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
Copy link
Author

paluh commented Aug 21, 2020

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
Copy link
Owner

robertdp commented Aug 21, 2020

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
Copy link
Owner

@paluh I finally got around to making a README.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants