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

Comparison with React (or any virtual-DOM based library) #113

Closed
saurabhnanda opened this issue Nov 14, 2016 · 3 comments
Closed

Comparison with React (or any virtual-DOM based library) #113

saurabhnanda opened this issue Nov 14, 2016 · 3 comments
Labels

Comments

@saurabhnanda
Copy link

Over at https://github.com/vacationlabs/haskell-webapps we're almost through with our POC of Reflex-DOM. We're about to start our POC of the same app with either Pux or Thermite

While we will be in a position to juxtapose Reflex with React-like libraries at the end of this exercise, we'd like to know your opinion on this matter. What does Reflex-DOM solve that React-like libraries don't? What does Reflex-DOM solve more elegantly/efficiently than React-like libraries?

@ryantrinkle
Copy link
Member

ryantrinkle commented Nov 14, 2016

@saurabhnanda Really what it comes down to is whether you prefer one semantics over the other. React-like libraries are based around a pure function from model to DOM, with a pre-set way of flowing events back up to modify the model. This has two main implications. First, since the entire DOM is potentially regenerated whenever the model changes, it needs to be very clever about using virtual DOM to avoid actually re-rendering things all the time. Second, it doesn't have first-class Events, so if you need a different flow, you have to go back to imperative state management.

Reflex, on the other hand, is based around a builder that is explicitly run only once, but that supports using Dynamic values and Events to modify the DOM after it has been produced. This means that change is explicitly modeled, giving you full control. Furthermore, since these types implement many common Haskell typeclasses, such as Functor, and in Dynamic's case, Applicative and Monad, working with changing values explicitly is not difficult.

Since Reflex deals with change explicitly, and since Events are first-class, you can easily create custom event flows for your application - and Reflex will guarantee that they're well-behaved, pure programs. Common event flows can be enshrined into monad transformers, such as PostBuild (things that are notified when their build step has finished) and PerformEvent (things that can trigger an action to be run when an event fires). These flows are actually higher-level than reflex-dom: they can be used with any reflex-based application, even if it is not DOM-based.

And, of course, one huge advantage of Reflex is that it puts the full power of Haskell in your hands - something few other web frameworks can do. For me, the main motivation behind FRP was that I knew pure functional programming, especially with Haskell in particular, was way more productive for me than old fashioned imperative programming. However, I didn't have a pure functional approach to GUI programming, so writing GUIs in Haskell felt like a step backwards compared with all my other Haskell programming. Reflex has made it possible for me to write GUIs in a way that feels "right" for Haskell, and gains all the benefits that I've come to expect from pure functional programming.

@saurabhnanda
Copy link
Author

I'm taking the liberty of rebutting (bordering on nitpicking) some assertions and statements, with the intended purpose of making the motivation for Reflex-FRP absolutely clear.

First, since the entire DOM is potentially regenerated whenever the model changes, it needs to be very clever about using virtual DOM to avoid actually re-rendering things all the time.

Yes, this is an engineering challenge, but has already been solved fairly well by other virtual DOM diffing libraries. Do you feel that the solution is subpar, or a big giant hack, or prone to breakage? IIUC it's a tree diffing algorithm, which I'm assuming is a well-studied subject.

Second, it doesn't have first-class Events, so if you need a different flow, you have to go back to imperative state management.

Would it be possible to explain the concept of Event that you're referring to? To an untrained eye the whole DOM API is structured around events, such as, onclick, onblur, onfocus, etc.

Secondly, any example of a "different flow"? What exactly are we talking about here?

This means that change is explicitly modeled, giving you full control.

I'm assuming this statement means that other dom-diffing libraries do not model change explicitly. Is that really true? Doesn't React conceptually have this flow:

new state = f (old state, event)

Where event is what is explicitly triggering the change. How exactly is this not giving us "full control"?

Since Reflex deals with change explicitly, and since Events are first-class, you can easily create custom event flows for your application - and Reflex will guarantee that they're well-behaved, pure programs.

Again, request you to please provide an example of "custom event flows"

@jdreaver
Copy link

Do you feel that the solution is subpar, or a big giant hack, or prone to breakage? IIUC it's a tree diffing algorithm, which I'm assuming is a well-studied subject.

I've had a few problems with React's virtual DOM implementation. I've had to sprinkle a lot of shouldComponentUpdate() calls when creating fairly large plots. If one component of a plot depended on mouse position, React would try to look at the props of all the components in the plot whenever the mouse position changed. (As a concrete example, this plot was similar to the plot on the home page of techanjs: http://techanjs.org/). The problem with shouldComponentUpdate() is it's easy to mess up the implementation if you are trying to do something clever, and if you add props it can be easy to forget them to shouldComponentUpdate().

Otherwise I am actually pretty happy with React's virtual DOM. I just think Reflex's approach is nice too because you can be fairly certain that the only thing being updated is exactly what is touched by some changing Event. You don't have to worry about the virtual dom under the hood.

Would it be possible to explain the concept of Event that you're referring to?

I think Event here is literally the Event t a type in Reflex. I wouldn't say that onclick, onblur, etc are "the DOM API". Yes, they are a component of the DOM API, but they are just a small component only dealing with user interaction. In Reflex, Events are actual pieces of data that are designed to be passed around and used in functions, not just carry a little piece of state that describes a user interaction.

Secondly, any example of a "different flow"? What exactly are we talking about here?

I think "different flow" means not using React's fairly strict props/state system. Reflex is flexible because you can imitate that exact system if you want fairly easily; props are Event types passed into a component and state can be a Dynamic that is internal to a component. However, since reflex components are just Haskell functions, and we have all the power of Haskell, we can do all kinds of things like:

  • Embed events in a ReaderT (the only React counterpart to this is the controversial Context)
  • Use currying to partially apply common parts of components (in React you have to create a new wrapper I believe)
  • Make a list of components and ensure they all have the same API thanks to the type system.

Basically, React forces you to use their Component class and implement specific methods to get things done, and things that don't fit neatly into that object oriented mold are hard to do. Reflex is based on functions, first-class Events, and a monadic context that handles the DOM for you, so it is naturally much more flexible.

Doesn't React conceptually have this flow:

new state = f (old state, event)

Where event is what is explicitly triggering the change. How exactly is this not giving us "full control"?

You're conflating React and Redux here. I actually really like Redux, but again you can adopt an architecture like this in Reflex as well (top-level global state in a store that is only changed with Events). I think @ryantrinkle was actually saying that's a good model, and Reflex can do it well. However, that is not built into React.

Again, request you to please provide an example of "custom event flows"

  1. We can have a full Redux-style architecture where all state is contained in a single Dynamic t MyAppState. The only way to change it is via Events that are created from components, and we can enforce this via the component APIs and the type system. I've done something like this, and it's very enjoyable in Haskell/Reflex.
  2. We can do something more like pure React, where there is no global app state, but we just pass around individual Events where they need to go and we can have internal Dynamic state for some components. This is useful when you don't want to expose an implementation detail for a widget to the global state.

Those are two extremes, but we can do anything in between. Again, since we are using simple functions and Haskell, composability is king, and we aren't restricted to fitting our components into a Class API (React) or the Redux reducer system.


Again, I really like React and Redux. I just think Reflex and FRP are superior methods of organizing interactive programs.

I think that one huge advantage of the React/Redux combo is the top-level architecture of your app is laid out plainly both with conventions and APIs; everyone has a very similar architecture when using those two libraries together. You can jump on a new project and get productive very quickly. With a more flexible approach like Reflex's, the chance of that happening is much lower.

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

No branches or pull requests

4 participants