liqula / react-hs

A GHCJS binding to React based on the Flux design. The flux design pushes state and complicated logic out of the view, allowing the rendering functions and event handlers to be pure Haskell functions.
32 stars 10 forks source link

Make `StoreAction` injective to improve type inference #20

Closed tysonzero closed 7 years ago

tysonzero commented 7 years ago

Currently we most do someStoreAction @MyStore MyAction, but if we made StoreAction injective then this type application would not be necessary, any we could just do someStoreAction MyAction.

The downside being that two stores can't share the exact same actino type, but is that ever really done? And in any case any situation where you want to do that can be replaced with simply using a newtype.

fisx commented 7 years ago

Does the newtype really help with this? Don't we want to trigger any number of stores by triggering a single action, not one for every store we want to target?

(Not sure I understand this all. I'm not 100% convinced of the flux approach anyway, I've only ever used one global store so far.)

tysonzero commented 7 years ago

Don't we want to trigger any number of stores by triggering a single action, not one for every store we want to target?

I mean we don't do that right now... so this would not be a regression in that regard.

someStoreAction only ever directly triggers one store currently, and will continue to only trigger one store.

The newtype thing would purely be to have to stores of different types have the same action type.

(Not sure I understand this all. I'm not 100% convinced of the flux approach anyway, I've only ever used one global store so far.)

Wait really? How do you generally manage state, stateful views? Can we have an example of such an approach in the examples directory.

fisx commented 7 years ago

I mean we don't do that right now.

you're right. sorry, i was being confused. (-:

How do you generally manage state, stateful views?

there is a difference between local states and global state. local states are not updated via actions, global states are. the one and only global state in the todo example app is the latter. we can grow this arbitrarily without having to split it up into disconnected states, and as long as we only have one global state, we have to worry a lot less about concurrency. i think that's the idea of reflux, and it feels very haskell-y to me.

i can only see three reasons not to switch from flux:

tysonzero commented 7 years ago

It seems like TestClient.hs does actually currently share an action type between multiple stores, but I personally don't think that the way it is used is all that important, so I will probably look into changing that, and then you can take a look and see what you think.

And I see, so you are advocating for just 1 global state. I have been personally breaking up my state into multiple stores, and it does seem decently nice for modularity, so I would probably lean toward keeping multiple stores vs forcing there to be just 1.

I do think that perhaps more can be done with local state, perhaps local actions to accompany that state? Perhaps I'm still too attached to my earlier stance from the other github issue that I don't like mutable global things.

fisx commented 7 years ago

I don't like mutable global things.

How do you cope with the fact that a UI is very much a mutable global thing? (-:

I think it's not the best approach to force everything into being a pure function. It's good to factor out as much purity as feasible in any given problem, but there will always be some effectful part left to model, and in this case I think a mutable global state is one of the better models.

It seems like TestClient.hs does actually currently share an action type between multiple stores [...]

cool, didn't know that. so perhaps we should keep this as an example how multiple stores can be done, and if you refactor that you do it in a clone, so new users can later compare the two approaches. if we form a consensus on which is better, we can document that. but for now i agree that it would be good to leave this design decision to the library users.

tysonzero commented 7 years ago

I mean programs as a whole are mutable and global, doesn't mean they can't be 99.99% some combination of pure and local mutable things, with a tiny little main that converts those components into a global mutable thing.

I think it's not the best approach to force everything into being a pure function. It's good to factor out as much purity as feasible in any given problem, but there will always be some effectful part left to model, and in this case I think a mutable global state is one of the better models.

I'm mostly arguing against the global part, the mutable part I have fully accepted the necessity of.

I mean standard React doesn't really have global mutables right? It has react elements which can be freely nested and duplicated and so on. I actually kind of like that approach, just without the whole "JavaScript" part, and with the mutable state of an element separated from the rendering and such of that element. It seems to me as though stateful views might be what I want to look into?

fisx commented 7 years ago

true, react does not have global mutables, but in a way it's cheating: it acknowledges that global state is tricky to get right, and leaves it to others. that's why there is flux, redux, and possibly other approaches that complement react with global state.

so if i understand you correctly your position is this: global state is needed, sure. but we can create a local new MVar in main and just pass it around through the entire program, that's cleaner than even a single global unsafePerformIO.

my answer to this is this: the cost of passing around an extra function argument to all functions everywhere is quite high. having a single place where a global state is created is well-understood and it happens in many libraries, and it is far more convenient and efficient. i'm not saying there isn't anything better than that, i'm just saying it's the better choice of the two we're discussing.

tysonzero commented 7 years ago

That was my original argument. My new argument is that perhaps with stateful views, as long as enough effort is put into supporting actions that act on stateful views, could be a nice replacement for stores, and thus the mutation would be local and not global.

Can we have an example with stateful views / a tutorial on how to use them well? Because I know it is possible to use them that way, since that is how my js react website is setup.

fisx commented 7 years ago

ah, ok, got it. i think :-)

i've used local state for keeping track of user input in forms, but i'm unclear about under what circumstances the state is whiped: can't be every re-render, but probably if it gets removed from the dom and re-added? but yes, it will help you keeping the global store smaller and cleaner, and it should also help with performance.

i'm all for an example, but it will be a while until i have time to add it myself. a PR would be very welcome. (-:

tysonzero commented 7 years ago

I think it is relatively safe from being lost, at least when I used react-router and react with the react elements / views each holding their own state I never ran into issues.

Once I have it figured out better I can look into making a PR, to be perfectly honest I am still not super sure how all of react-hs fits together yet, plenty of learning and source diving to go.

I wonder if it is possible to pass in event receivers of sorts into child views (when dealing with stateful views that is), so that child elements can send actions that parents know about, without dealing with global stores. Do you know if that is possible?

While I do think its good to have support for flux architecture, I do think that having this library be the de-facto react library and support other architecture (such as just old fashioned react) would be really nice, I am happy to help make that possible.

fisx commented 7 years ago

I am skeptical about introducing other means of passing around state. The nice thing about flux and redux is its simplicity. if there are other channels besides actions being passed out of and props / state / store being passed into components, at the very least it should not make a difference in robustness and clarity for people who don't want to use them.

having that said, i'm also still learning about composition in this setting. if you want to play with this idea, and won't be too frustrated should i decide not to adopt your results, i'd be interested to discuss them.

i guess a first start would be to agree on where react ends and flux starts. in my limited understanding, react is just components and event handlers, and flux is global store, actions, dispatching actions, and feeding stores back into components (where at some point they turn into something like a property).

tysonzero commented 7 years ago

Yeah I would say React is views (both stateful and not) / components / event handlers. Stores and store actions are the Flux part.

And I am not really talking about an overall or some big new thing, just improving the way stateful views work, and I sure hope you don't plan on removing them, that would be a big mistake IMO, as they are a fundamental part of react.

fisx commented 7 years ago

I sure hope you don't plan on removing them

removing stateful views? no, agreed, that would be stupid. they will stay. (-:

tysonzero commented 7 years ago

I've changed my opinion on this. Since often I have found it useful to just make random built in or at least types that aren't solely designed to be actions the action type. So doing this would make that risky and bad practice. Feel free to close this unless you see any other reason to keep it open.

fisx commented 7 years ago

Thanks!