Hacker News new | ask | show | jobs
by satvikpendem 1207 days ago
I don't really understand why people like signals again. I used a signals-like reactive programming model in VueJS a while ago and hated that you could never be sure exactly where things were being changed. Thankfully it seems that the React creators and maintainers are not hopping on the signals train and are instead adamant about unidirectional dataflow with explicit mapping of state to UI, which, as has been the reason for why React had been invented in the first place, makes reasoning about the application much easier [0][1].

This is a good thread about their drawbacks [2]. Apparently, both the below examples actually do different things:

    function One(props) {
      const doubleCount = props.count * 2;
      return <div>Count: {doubleCount}</div>
    }


    function Two(props) {
      return <div>Count: {props.count * 2}</div>
    }
Like the tweeter says, signals are mutable state. I'll stick with unidirectional data flow in React.

[0] https://twitter.com/jordwalke/status/1629663133039214593

[1] https://twitter.com/dan_abramov/status/1629539600489119744

[2] https://twitter.com/devongovett/status/1629540226589663233

13 comments

That example makes a lot of sense once you realize how JSX works in SolidJS (and its very different from React):

1. The function is called only once at component construction, and never again. Unlike React, it is _not_ called for every render.

2. Every expression in `{}` is compiled to a thunk function - not to a direct JS expression.

For example, the expression

   <>{props.count * 2}</>
compiles to

    memo(() => props.count * 2)

Another interesting bit - as the article above rightfully points out, `useState` and `useMemo` are signals. The main difference is that you have to track and specify your dependencies manually, whereas Solid auto-tracks dependencies based on their usage while the computation runs.
Yes, it's more of a design problem of some of these libraries though.

Ideally, signalling shouldn't be retrofitted on normal variables. It assumes then that you overload variable access (assignment and reads). Then it's hard to know which variables are reactive and which ones are not. The problem stems from the overloading of semantics.

A UI is mutable so it's not about mutable state being wrong but the unit of variability are not program variables but rendered props which are basically DOM Element properties. In react, it just happens that it uses variables whose values are injected in the DOM elements via jsx but that's an issue (especially when dealing with exports, prop drilling etc)

So until a plain variable and a reactive variable are made to look different everywhere in code, it will remain confusing. And even then, it's not optimal.

Disclaimer: I'm currently implementing a cross-platform UI framework in Go and that's the reason why I had to think about it since traditional languages do not have reactive variables.

If those two pieces of code do different things, it’s a bad library plain and simple lol.

I’ve implemented this pattern with mobx and those two pieces of code are equivalent.

In my opinion:

- Unidirectional data flow patterns prevent a class of problems junior developers tend to make but require much deeper discipline to keep the app understandable

- Conventional bi-directional data flow patterns are susceptible to junior developer mistakes but generally are hard to make unreadable

Elm managed to move away from signals back in 2016 (controversial at the time, but IMO a great move).

https://elm-lang.org/news/farewell-to-frp

Controversial only because they were hard to learn and those that invested in learning lost that investment. The new architecture is so much better and not hard to explain.
This one sentence probably explains 90%+ of all programming advocacy.
So sad Elm is essentially dead.
I'm not a JS/Vue/React programmer but I would definitely expect function One to return <div>Count: 4</div> if props.count == 2 when One is called. For function Two I would be more suspicious and would not be overly surprised if it returned <div>Count: {props.count * 2}</div> to be evaluated at a later stage.
Because it's nicer. The mental model of reading the functions using signals is; it's all about initialize|constructor|new function. And then those off-jsx stuff starts to make sense since you won't re-initialize those things. They need to exist somewhere in particular execution model. In Solidjs, it's effects (compiled from jsx where signals are read). I think those who are familiar with static type languages, naturally understand how signals needs to be unwrap/wrap .. like a monoid, not a big deal.
When you have a big app, it becomes a spaghetti mess, and I speak from personal experience having to untangle that spaghetti.
Counterpoint: I've worked on a big application that used MobX and had no such issues.
What approach do you prefer for big apps?

The state has to go somewhere, curious how you choose to handle it.

Personally I just use Redux Toolkit with RTK Query for any synchronization with backend state. What I like about Redux is that it takes the immutable functional approach, you cannot change previous state but must instead derive new state from the old state very explicitly. In this way, it's the antithesis of signal-based state.
> What I like about Redux is that it takes the immutable functional approach

Strange that you don't like spaghetti, but then praise Redux that is spaghetti exemplified: dozens of functions, and wrappers, and hooks eerywhere. Trying to trace how a value gets changed through the three-four layers of wrappers is like pulling teeth through the anus.

And looking at the current reincarnation of this abomination, it is marginally less spaghetti, and converging on the same signals code that everyone is converging on.

Redux Toolkit is very impressive, especially with TypeScript. I'd agree that there was a lot of boilerplate in the before-times, but today, it's not "marginally less," it's significantly less boilerplate. The debugging tools are top notch as well, I can trace values and their changes pretty easily these days.

The main point of Redux is that it follows functional programming paradigms of immutable data, which makes reasoning about changes much easier. Compare that to the global state everywhere of signals/reactive/observables-based approaches like MobX or RxJS.

That difference in the snippet you mentioned has 100% to do with how Solid is compiling its JSX, and 0% to do with how signals must work.

If you don't like what Solid is doing you may consider it a self-inflicted wound, nothing is forcing Solid to work that way.

Your 2 is not a signals drawback, but a SolidJS one.

I love signals in VueJS, and it doesn’t even preclude a redux-like/lite state- management library like Pinia.

I would rather use the proper Vue term, reactive references/refs. Signals is such an awkward term.

https://vuejs.org/guide/extras/reactivity-in-depth.html#conn...

That’s why I italicized it.
I haven't had much success using signals as a software composition mechanism at scale ... Despite being familiar with Conal Elliott's "functional reactive animations" concepts and Racket's FrTime.

"patch languages" like PureData, Max/MSP/Jitter, QuartzComposer are much better interfaces to work with signals ... Or rather with signal processors.

That sounds like a limitation of solid. In Vue, you can use return type to differentiate if you are in ts atleast
here’s a debunking of the third tweet with reference: https://twitter.com/antonycourtney/status/162970259437243597...
A series of articles from the author of Solid discussing all those tweets, and more, including the history of signals and their vurrent state.

- The Evolution of Signals in JavaScript, https://dev.to/this-is-learning/the-evolution-of-signals-in-...

- React vs Signals: 10 Years Later, https://dev.to/this-is-learning/react-vs-signals-10-years-la...

- Making the Case for Signals in JavaScript , https://dev.to/this-is-learning/making-the-case-for-signals-...

Those two code examples are different because that's the tradeoff that Solid made. It allows Solid to track changes in a uniform way even if the data isn't inside a component. Because in Solid components are just a way to organize code, not a unit of rendering (like in React).

And on top of that if you think that React still somehow has unidirectional data flow, it doesn't. Hooks make it anything but unidirectional. And not that different from signals: https://res.cloudinary.com/practicaldev/image/fetch/s--upMC6... But often less predictable.

See also The Cost of Consistency in UI Frameworks, https://dev.to/this-is-learning/the-cost-of-consistency-in-u... which is slightly related to this.

Dan Abramov replied to those as well. Personally, the purity of the React model with bringing in only local rather than global state in every function is what appeals to me. Even hooks are still local state, if you change the value in useState, you're not going to suddenly have something change elsewhere, where it wasn't explicit where you changed it.
useEffect, useContext, and usSyncExternalStore are no longer about "local only".

And you never use only local state. You advocate for Redux in a different comment which is literally a global state where you are going to have something suddenly change somewhere from the point of view of a component.

You are misunderstanding me. I never said to use only local state with no global state at all. My point is that signals automatically make all state global because you can reference what should be a local value in a component, in another component entirely which is not at all connected to the initial component either in a parent or a child relationship. In other words, you are making a graph, not a tree.

For Redux, at least that's explicit, for signals, it's really not.

My impression is that Javascript is just an horrible language to create reactive systems, and that default purity with explicit effects is extremely underrated. (But then, I was already biased.)

It is hard to even make sense of the discussion without imagining the details of how those frameworks are implemented. And the discussion on the level of normal usage, not something advanced or development oriented.

What language(s) would you recommend for building reactive systems?
Well, the OP links to 3 large threads from JS framework designers that do nothing but talk about how they deal with the lack of referential transparency.

So, I'd guess, something with referential transparency.