DEV Community

Discussion on: Is VDom still faster?

marzelin profile image
Marc Ziel • Edited on

Hooks (useState() in particular) give the stateful function components access to React-managed mutable state — so same inputs (i.e. function arguments), same results is not guaranteed.

When you see Haskell do notation:

do { putStr "Hello"
   ; putStr " "
   ; putStr "world!"
   ; putStr "\n" }
Enter fullscreen mode Exit fullscreen mode

you might say: Look ma, imperative statements and side effects - Haskell is like Java. But there is much more than what meets the eye, isn't it? do notation is just syntactic sugar that translates directly to monadic operations.

Dan Abramov:

Finally, if you’re a functional programming purist and feel uneasy about React relying on mutable state as an implementation detail, you might find it satisfactory that handling Hooks could be implemented in a pure way using algebraic effects (if JavaScript supported them).

again Dan Abramov:

conceptually you can think of useState() as of being a perform State() effect which is handled by React when executing your component. That would “explain” why React (the thing calling your component) can provide state to it (it’s above in the call stack, so it can provide the effect handler). Indeed, implementing state is one of the most common examples in the algebraic effect tutorials I’ve encountered.

Let's do some example to see what's this all about.

Having this component:

function Component() {
  const [name] = useState();
  const [greeting] = useState();
  return <p>{greeting}, {name}!</p>
}
Enter fullscreen mode Exit fullscreen mode

we can see that it escapes outside its boundaries twice by calling useState. useState call yields control to React and React passes in some data and then gives control back to the component. Hm... yielding and then taking back control... It seems like a generator, so let's make it a generator:

function* Component() {
  const [name] = yield;
  const [greeting] = yield;
  return <p>{greeting}, {name}!</p>
}
Enter fullscreen mode Exit fullscreen mode

It looks a bit weird but when you look at how it's used:

const iter = Component();
iter.next()
iter.next(["Greg"])
iter.next(["Hello"])
Enter fullscreen mode Exit fullscreen mode

You'll see that it's just a curried function:

function Component() {
  return ([name]) => {
    return ([greeting]) => {
        return <p>{greeting}, {name}!</p>
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

That can be shortened to:

const Component = () => ([name]) => ([greeting]) => 
    <p>{greeting}, {name}!</p>
Enter fullscreen mode Exit fullscreen mode

As you can see it's just a pure function - the only thing that is special about it is that it can be called in three separate steps and this doesn't break its purity. Yielding to the caller for more data in the middle of operation is completely legal - that's what IO Monad is doing under the hood.

Summing up, hook notation is like do notation - it doesn't make the code impure; it's just an abstraction that makes working with the code easier.

From React design principles:

React is pragmatic. It is driven by the needs of the products written at Facebook. While it is influenced by some paradigms that are not yet fully mainstream such as functional programming, staying accessible to a wide range of developers with different skills and experience levels is an explicit goal of the project.

and:

We try to provide elegant APIs where possible. We are much less concerned with the implementation being elegant. The real world is far from perfect, and to a reasonable extent we prefer to put the ugly code into the library if it means the user does not have to write it. When we evaluate new code, we are looking for an implementation that is correct, performant and affords a good developer experience. Elegance is secondary.


So what React has accomplished is that everyone can still work in their comfort zone of class-based object orientation while pretending to be functional

It's the other way around: React uses functional concepts that provide real benefits but serves them in a digestible form suited for plain JavaScript.

Thread Thread
peerreynders profile image
peerreynders

Haskell is like Java

The Curse of the Excluded Middle — "Mostly functional" programming does not work.

The ease with which you can define reusable abstractions over effects is why people often call Haskell the world's best imperative language.

do notation is just a language feature, not the defining characteristic of Haskell — being firmly based on lambda calculus is. The do notation is just imperative sugar over a functional core. Haskell doesn't pretend to be imperative.

Compare that to React which has a somewhat functional appearance (skin) over an essentially imperative core. React pretends to be functional.

In many ways React's true nature is best revealed by createReactClass (formerly React.createClass).

var specs = {
  render: function() {
    return React.DOM.span(null, "I'm so custom");
  }
};

var Component = React.createClass(specs);
Enter fullscreen mode Exit fullscreen mode

Even the naming is awkward — it really should have been React.createComponent - but to seem more current they went with Class in ES5 to seem more respectable in context of a OO mainstream.

The parameter being passed is a Crockford specification object:

  • React manages the component instances and their state that render against the VDOM.
  • To register a custom component one has to supply a set of functions (the specs specifying the behaviour for the custom component instances), each function handling one particular life cycle event. The render handler is mandatory while all the other events have default handlers. These life cycle handlers get access to React-managed component instance state via this.props and this.state (which is mutated via this.setState()) when they are invoked.

Even with hooks React's fundamental character is largely unchanged only that now there is only the render function (making it seem more functional) which in it's body has to somehow register/manage the handling of the other life cycle events.


you might find it satisfactory that handling Hooks could be implemented in a pure way using algebraic effects.

Algebraic Effects are reflected in the type system. That's why in Haskell input/out happens inside of the IO monad. Algebraic effects have no reflection in the type system of JavaScript, TypeScript or Flow - so there is no rigor behind it, no containment or isolation, and it's just the Wild, Wild West.


As you can see it's just a pure function

Generators are not pure functions. They are based on an internal mutable object/closure which means that they are not referentially transparent and therefore not pure.

And the whole "it's just a curried function" is just a sleight of hand. Currying solves a very specific problem.

Haskell Programming from First Principles — Chapter 1: Anything from Almost Nothing - 1.5 Beta Reduction, Free Variables

Each lambda can only bind one parameter and can only accept one argument. Functions that require multiple arguments have multiple, nested heads. When you apply it once and eliminate the first (leftmost) head, the next one is applied and so on. This formulation was originally discovered by Moses Schönfinkel in the 1920s but was later rediscovered and named after Haskell Curry and is commonly called currying.

i.e. to have multi-argument functions in an environment that only allows each function to have a single argument. useState() doesn't address that problem. useState() serves to have former component invocations affect later invocations and more importantly allows the "outside world" to have an effect on the component instance state. Even if this happens somewhat under the control of React there is no rigidly enforced compile and run time contract like there is with, for example, the IO monad.


Summing up, hook notation is like do notation - it doesn't make the code impure; it's just an abstraction that makes working with the code easier.

I disagree, the comparison between do notation and hooks is seriously flawed at best. Hooks make functions impure and are just a React specific mechanism for code organization and perhaps reuse - that's it and in that capacity they may be considered an abstraction of some sort.


React uses functional concepts that provide real benefits but serves them in a digestible form suited for plain JavaScript.

I continue to disagree - React also claims to be a "not a framework" when in fact it's manner of operation belies that claim; i.e. one has to take the claims made inside the React documentation with a bunch of salt.

Thread Thread
marzelin profile image
Marc Ziel • Edited on

The do notation is just imperative sugar over a functional core. Haskell doesn't pretend to be imperative.
Compare that to React which has a somewhat functional appearance (skin) over an essentially imperative core. React pretends to be functional.

When you compile your pure Haskell code the resulting machine code is imperative and effectful. What's more, Haskell compiler (GHC) is partly written in imperative languages like C. From your strict point of view that probably make it an impostor that pretends to be functional while having imperative core. ;)

React doesn't pretend to be functional. It clearly states that it is influenced by functional programming concepts and this is reflected in its API.

React can't have a fancy functional syntax that could please every purist because it isn't a compiled language but a view library.

Yes, a library. A library is just a bunch of code providing some functionality that can be shared and used easily in many applications. React fulfills that definition. It also fulfills the definition of a framework so you can call it a view framework if you wish, but "view framework" name is kind of already taken by another project.

But if you see a sentence:

React is not a framework like Angular but just a library for building user interfaces

and all you get from it is that "React is not a framework" then it's not the sentence that's wrong - it's your interpretation of the sentence.

In many ways React's true nature is best revealed by createReactClass (formerly React.createClass).
Even the naming is awkward — it really should have been React.createComponent - but to seem more current they went with Class in ES5 to seem more respectable in context of a OO mainstream.

It's called createClass because it creates a class. It was needed because creating creating classes in ES5 is really awkward.

useState() serves to have former component invocations affect later invocations and more importantly allows the "outside world" to have an effect on the component instance state.

useState() serves the same purpose as State Monad. The difference here is that state management is pushed out of your Haskell code and managed outside (Haskell compiler does the dirty work). React is just a bunch of JS code so the state is still kept within the realm of JavaScript. But no, "outside world" cannot directly change the state - even a component can't do it directly - all state changes must have to go through React setState call and only React can change it. React guards the access to component state in similar way as Haskell guards access to State Monad.

Even if this happens somewhat under the control of React there is no rigidly enforced compile and run time contract like there is with, for example, the IO monad.

Yes, it's not Haskell, it's JS so you still can do some weird stuff but React is rigid enough and doesn't give you many options to mess things up.

Hooks make functions impure and are just a React specific mechanism for code organization and perhaps reuse - that's it and in that capacity they may be considered an abstraction of some sort.

Yeah, you can be very picky about functional programming and only recognize the following definition:

Functional programs contain no assignment statements, so variables, once given a value, never change. More generally, functional programs contain no side-effects at all. A function call can have no effect other than to compute its result.

But this makes you like this:

The functional programmer sounds rather like a mediæval monk, denying himself the pleasures of life in the hope that it will make him virtuous. To those more interested in material benefits, these “advantages” are totally unconvincing.

If I can have more or less the same benefits as in a pure functional language but without having to switch from the most universal and ubiquitous language in the world that's a pretty good deal for me.

Thread Thread
peerreynders profile image
peerreynders • Edited on

When you compile your pure Haskell code the resulting machine code is imperative and effectful.

And during the compilation process the code is verified to comply with numerous constraints that permit the compiler to make some very specific and beneficial assumptions about the code.

React doesn't pretend to be functional.

I think here we have to differentiate between React and the React community. Now clearly React itself can't be responsible for the entire React ecosystem - but often the wording in the documentation or the statements by core members encourage certain notions in the community.

React's mental model is often oversimplified as view = fn(state) — which is clearly functional as it implies view generation from state through transformation by a pure function.

React actually doesn't work that way — there are two sources of state — props which is state as handed down from the owner component to the nested component and state which is "persisted and managed" by React on behalf of the component instance. So while components do generate the view they also can change state in the process - i.e. components are not enforced to be referentially transparent or in web terms idempotent. So React cannot grant the typical benefits or guarantees that a typical FP environment can.

React's reliance on immutability further feeds into the image of being functional.

Then there is the fact that the community often refers to Function Components as Functional Components.

So when it comes right down to it, one has to wonder how much of "hooks and stateful function components" actually had to do with necessary innovation or whether or not the whole "functional image" is about keeping React trendy within the web development community at large.

but "view framework" name is kind of already taken by another project.

React predates Vue by about a year and despite its name Vue doesn't get to monopolize an entire tool category by virtue of how its name sounds.

all you get from it is that "React is not a framework" then it's not the sentence that's wrong - it's your interpretation of the sentence.

but I also think it’s not a framework.

It's not my interpretation, it's an active image being defended by a large portion of the React community. A framework, due to inversion of control, exerts a significant amount of design pressure on the structure of the "client side architecture". Calling React a library is disavowing the influence that React has as evidenced by countless sources describing React as unopinionated. Just because React is less constraining than Angular does not imply that it is "unopinionated" — it's just a matter of degree — React is very much opinionated, just not as much as Angular.

It was needed because creating classes in ES5 is really awkward.

Did it return a constructor function? A factory function for component instances? At best it's bad naming because createComponent would have been more apt — at worst it's trendy naming because in 2013 "real programmers" used class-based object-orientation — not just plain functions and objects.

Generators are pure functions as long as you don't mutate state within them.

Most generators used in JavaScript are not pure. And JavaScript has no controlled runtime support for lazy evaluation, so lazy evaluation is emulated with mutable state inside the function's closure.

useState() serves the same purpose as State Monad.

"Purpose" perhaps but again without committing to any constraints. In order to preserve referential transparency functions operating within a state monad have a signature of (state) => [state, result]. React function components could have easily adopted a similar signature. One benefit would have been that it would be trivial to micro test components without having to provide an entire "React environment" for the component to run in.

And that's just the point — React does just enough to pander toward maintaining a "functional image" but doesn't commit to the necessary constraints that would afford developers the actual benefits of many FP techniques.

But no, "outside world" cannot directly change the state

Of course it can - that is the whole principle of how third party libraries like Redux integrate themselves with the React component tree — especially since the introduction of hooks. And even before that the "outside world" invaded the component tree via Higher Order Components (connect, Provider pattern) which are all still "React Components".

React guards the access to component state in similar way as Haskell guards access to State Monad.

There is no "guarding" of any constraints.

It maintains control for the purpose of detecting changes to component instance state and to maintain full control over view reconciliation and scheduling.

If I can have more or less the same benefits as in a pure functional language but without having to switch from the most universal and ubiquitous language in the world that's a pretty good deal for me.

So the the Curse of the Excluded Middle is just Erik Meijer ranting again? And he can rant.

The functional programmer sounds rather like a mediæval monk,

Frankly, I couldn't care less if React was functional or not — but I am getting tired of the pretentiousness exuded by it and a significant part of it's community.

For example wickedElements is not functional in nature at all but it is brilliant in the way it leverages JavaScript and the DOM ("the platform"). It is what it is and it is good at at it.

Meanwhile there seems to be a cult-like following around React unwilling to examine and evaluate the tool objectively but instead is willing to propagate half-truths:

  • React is not a framework — of course it is.
  • React is unopinionated — due to inversion of control it has a definite opinion, just not as severe as Angular.
  • React is functional in nature — it uses functions. So does the C language, neither of them is functional in the FP sense.
  • React is declarative — it manipulates the DOM so you don't have to. Meanwhile there are no benefits of declarative programming, it's just the usual BBoM; i.e. it's just an excuse to avoid learning how to manipulate the DOM or use something else that actually leverages the browser's strengths.