Hacker News new | ask | show | jobs
by leononame 807 days ago
Why do you think this is premature memoization? This is an example, boiled down to a simple function. Do you think people just came up with the use case for this without ever having needed it?

I think an effort in standardizing signals, a concept that is increasingly used in UI development is a laudable effort. I don't want to get into the nitty gritty about what is too much boilerplate and whether you should build an event system or not, but since signals are something that is used in a variety of frameworks, there might be a good reason to it? And why not make an effort and standardize them over time?

3 comments

> a concept that is increasingly used in UI development

For a desktop app developer that's a pretty funny statement, given that the Qt framework introduced signals and slots in the mid 90s.

I am curious how many web devs think that signals are a new concept. (I don't necessarily mean the parent poster.)

While they share the same name, and are both reactive primitives, there are some fairly key differences between these signals and the QT signals and slots mechanism.

The main one is that QT signals are, as far as I understand, a fairly static construct - as you construct the various components of the application, you also construct the reactive graph. This graph might be updated over time, but usually when components are mounted and unmounted. JS signals, however, are built fresh every time they are executed, which makes them much more dynamic.

In addition, dependencies in JS signals are automatic rather than needing to be explicitly defined. There's no need to call a function like connect, addEventListener, or subscribe, you just call the original signal within the context of a computation, and the computation will subscribe to that signal.

Thirdly, in JS signals, you don't necessarily need to have a signal object to be able to subscribe to that signal. You can build an abstraction that doesn't necessarily expose the signal value itself, and instead provides getter functions that may call the underlying signal getter. And this same abstraction can be used both inside and outside of other reactive computations.

So on the one hand, yes, JS signals are just another reactivity tool and therefore will share features with many existing tools like signals and slots, observables, event emitters, and so on. But within that space, they are also a meaningful difference in how that reactivity occurs and is used.

Thanks for the great reply! I definitely need to take a closer look.
This is an interesting topic so I tried to dive in a bit.

From my reading I understood that Qt signals & slots (and Qt events) are much more closely related to JavaScript events (native and custom).

In both you can explicitly emit, handle, listen to events/signals. JavaScript events seem to combine both Qt signals & slots and Qt events. Of course without the type safety.

For example, taken from https://doc.qt.io/qt-6/signalsandslots.html

"Signals are emitted by objects when they change their state in a way that may be interesting to other objects."

However what I think they are proposing in the article is a much more complex abstraction: they want to automate it so that whenever any part of a complex graph of states changes, every piece of code depending on that specific state gets notified, without the programmer explicitly writing code to notify other pieces of code, or doing connect() or addEventListener() etc.

What are your thoughts on that? I'd be interested to hear since I'm sure you have more experience than me.

This sounds interesting. The code examples reminded me of Qt signals but all the answers to my post suggest that JS signals would be much more powerful. Honestly, I'd need to take a closer look.
JS signals come from functional reactive programming, which is a generalization of synchronous reactive programming from the Lustre and Esterel programming languages from the 80s and 90s. I believe the first version was FrTime published in 2004.

You can think of reactive signals as combining an underlying event system with value construction, ultimately defining an object graph that updates itself whenever any of the parameters used to construct it change. You can think of this graph like an electronic circuit with multiple inputs and outputs, and like a circuit, the outputs update whenever inputs change.

> I don't want to get into the nitty gritty about what is too much boilerplate and whether you should build an event system or not

You're basically saying you want this thing, but you don't want to have to justify it

The rationale for it is the fact that multiple frameworks provide their own versions of this mechanism. The proposal is to relocate extremely popular and common functionality from framework space to the language/runtime space. The popularity of React is itself the rationale for the utility of this idea, and any terse version of the rationale is for show. Is that a good enough rationale? Maybe, maybe not, but you are shooting the messenger.
Most importantly: OP is right re: vanilla example is most legible. Reading the proposal, I have no idea what this "Signal" word adds other than complexity.

Less important: I really, really, really, really, am reluctant to consider that is something that needs standardizing.

Disclaimer: I don't have 100% context if this concept is _really_ the same across all these frameworks.

But frankly, I doubt it, if it was that similar, why are there at least a dozen frameworks with their own version?*

Also, I've lived through React, Redux, effects, and so on becoming Fundamentally Necessary, until they're not. Usually when it actually is fundamental you can smell it outside of JS as well. (ex. promises <=> futures). I've seen 1000 Rx frameworks come into style and go out of style, from JS to Objective-C to Kotlin to Dart. Let them live vibrant lives, don't tie them to the browser.

* I know that's begging the question, put more complex: if they are that similar and that set in stone that its at a good point to codify, why are there enough differences between them to enable a dozen different frameworks that are actively used?

> Disclaimer: I don't have 100% context if this concept is _really_ the same across all these frameworks.

Very nearly[1] every current framework now has a similar concept, all with the same general foundation: some unit of atomic state, some mechanism to subscribe to its state changes by reading it in a tracking context, and some internal logic to notify those subscriptions when the state is written. They all have a varied set of related abstractions that build upon those fundamental concepts, which…

> But frankly, I doubt it, if it was that similar, why are there at least a dozen frameworks with their own version?*

… is part of what distinguishes each such framework. Another part is that state management and derived computations are only part of what any of the frameworks do. They all have, beyond their diverse set of complementary reactive abstractions, also their own varied takes on templating, rendering models, data fetching, routing, composition, integration with other tools and systems.

Moreover, this foundational similarity between the frameworks is relatively recent. It’s a convergence around a successful set of basic abstractions which in many ways comes from each framework learning from the others. And that convergence is so pervasive that it’s motivating the standardization effort.

This especially stands out because the reference polyfill is derived from Angular’s implementation, which only very recently embraced the concept. From reading the PR notes, the implementation has only minor changes to satisfy the proposed spec. That’s because Angular’s own implementation, being so recent, internalizes many lessons learned from prior art which also inform the thinking behind the spec itself.

This is very much like the analogy to Promises, which saw a similar sea change in convergence around a set of basic foundational concepts after years of competing approaches eventually drifting in that same direction.

[1]: Most notably, React is unique in that it has largely avoided signals while many frameworks inspired by it have gravitated towards them.

what makes useState different than signals?!
Explicit vs implicit dependencies (useEffect vs Signal.Computed/effect) and the fact that signals in contrast to useState can be used outside of react context which I assume is a good thing.

I personally mostly prefer more explicit handling of "observable values" where function signatures show which signals/observables are used inside them.

They’re very similar, and you can definitely squint right to see them as fundamentally the same concept… if while squinting you also see a React component itself as a reactive effect. Which is all technically correct (the best kind), but generally not what people mean when they’re talking about signals in practical terms.
Signals are fine grained reactivity. React is coarse grained reactivity. Legend-state adds signals to React and I'd recommend it over Redux/zustand which we used to use.
> why are there enough differences between them to enable a dozen different frameworks that are actively used?

Because they are not in the standard library of the language? Because they all arrived at the solution at different times and had to adapt the solution to the various idiosyncratic ways of each library? Because this happen in each and every language: people have similar, but different solutions until they are built into the language/standard library?

> Most importantly: OP is right re: vanilla example is most legible. Reading the proposal, I have no idea what this "Signal" word adds other than complexity.

The aim is to run computations or side effects only when the values they depend on change.

This is a perfectly normal scenario and you don't want to update all data the UI of a full application tree whenever something changes.

DOM updates are the most popular example but it could really be anything.

Of course in simple examples (e.g. this counter) you might not care about recomputing every value and recreating every part of the DOM (apart from issues with focus and other details).

But in general, some form of this logic is needed by every JS-heavy reactive web app.

Regardless of the implementation, when it comes to that, I'm not sure I see the benefit of building this into the language either.

>Usually when it actually is fundamental you can smell it outside of JS as well. (ex. promises <=> futures).

Excellent criterion

> But frankly, I doubt it, if it was that similar, why are there at least a dozen frameworks with their own version?*

Welcome to the fashion cycle that is JavaScript. Given a few years, every old concept gets reinvented and then you have half a dozen frameworks that are basically the same but sufficiently different so that you have to relearn the APIs. This is what I think standardization helps circumvent

A good standard library prevents fragmentation on ideas that are good enough to keep getting reinvented

> But frankly, I doubt it, if it was that similar, why are there at least a dozen frameworks with their own version?*

To answer this specifically: signals are a relatively low-level part of most frameworks. Once you've got signals, there are still plenty of other decisions to make as to how a specific framework works that differentiate one framework from another. For example:

* Different frameworks expose the underlying mechanism of signals in different ways. SolidJS explicitly separates out the read and write parts of a signal in order to encourage one-way data flow, whereas Vue exposes signals as a mutable object using proxies to give a more conventional, imperative API.

* Different frameworks will tie signals to different parts of the rendering process. For example, typically, signals have been used to decide when you rerender a component - Vue and Preact (mostly) work like this. That way, you still have render functions and a vdom of some description. On the other hand frameworks like SolidJS and Svelte use a compiler to tie signal updates directly to instructions to update parts of the DOM.

* Different frameworks make different choices about what additional features are included in the framework, completely outside of the signal mechanism. Angular brings its own services and DI mechanism, Vue bundles a tool for isolating component styles, SolidJS strips most parts away but is designed to produce very efficient code, etc.

So in total, even if all of the frameworks shared the same signals mechanism, they'd all still behave very differently and offer very different approaches to using them.

As to why different frameworks use different implementations as opposed to standardising on a single library, as I understand it this has a lot to do with how signals are currently often tied to the component lifestyle of different frameworks. Because signals require circular references, it's very difficult to build them in such a way that they will be garbage collected at the right time, at least in Javascript. A lot of frameworks therefore tie the listener lifecycle to the lifecycle of the components themselves, which means that the listeners can be destroyed when the component is no longer in use. This requires signals to typically be relatively deeply integrated into the framework.

They reference this a bit in the proposal, and mention both the GC side of things (which is easier to fix if you're adding a new primitive directly to the engine), and providing lots of hooks to make it possible to tie subscriptions to the component lifecycle. So I suspect they're thinking about this issue, although I also suspect it'll be a fairly hard problem.

Fwiw, as someone who has worked a lot with signals, I am also somewhat sceptical of this proposal. Signals are very powerful and useful, but I'm not sure if they, by themselves, represent enough of a fundamental mechanism to be worth embedding into the language.

> ...but since signals are something that is used in a variety of frameworks...

...common usage is not really a justification for putting it into the language standard though. Glancing over the readme I'm not seeing anything that would require changes to the language syntax and can't be implemented in a regular 3rd-party library.

In a couple of years, another fancy technique will make the rounds and make signals look stupid, and then we are left with more legacy baggage in the language that can't be removed because of backwards compatibility (let C++ be a warning).

From what I understand, a few/many of the big frameworks are converging on signals, and another commenter said that Qt had signals in the 90s https://news.ycombinator.com/item?id=39891883. I understand your worries, and I would appreciate some wisdom from non-JS UI people, espcially if they have 20+ years of experience with them.
Every framework is moving to signals, apart from React and I'd say if this became a standard even they will. This is like Promise. It's a sensible shared concept.