[HN Gopher] A proposal to add signals to JavaScript
___________________________________________________________________
A proposal to add signals to JavaScript
Author : beeman
Score : 149 points
Date : 2024-03-31 17:48 UTC (5 hours ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| nullvoxpopuli wrote:
| Socials:
|
| - Reddit:
| https://www.reddit.com/r/javascript/comments/1bsgnf5/tc39_pr...
|
| - X/Twitter:
| https://twitter.com/nullvoxpopuli/status/1774496900915327100
|
| - Masto:
| https://mastodon.coffee/@nullvoxpopuli/112191605019758002
|
| - Blue Sky:
| https://bsky.app/profile/nullvoxpopuli.bsky.social/post/3koz...
| senoralligator wrote:
| Surely it would be better to fix the interoperability issues of
| the language? "Lets just add a single implementation of
| everything to the standard!" seems like quite a strange response
| to "users of the language are having trouble with
| interoperability due to false coupling."
| jauntywundrkind wrote:
| I don't think the language hurts or hinders interop much? It
| has a wide range of tools that can adapt objects between
| different shapes, for when we do have two similar but different
| interfaces we are trying to bridge.
|
| I struggle to see what more one could want. Do you have any
| specific features you think would help a massive ecosystem of
| packages be able to work together, when for example different
| packages have different Signal implementations?
| senoralligator wrote:
| Better ways to describe polymorphism, a better type system in
| general really. Look to async Rust for a great example of
| such interoperability.
| nullvoxpopuli wrote:
| is it strange?
|
| Vue reactivity isn't compatible with Svelte, nor Angular.
|
| As a counter example to your question, what if we all had
| competing implementations of the object primitive. Libraries
| would barely work with one another and would need an interop
| layer between them (just as reactivity layers do today!)
| senoralligator wrote:
| I'll admit I don't use JavaScript very often, but surely the
| state of polymorphism could be improved? For example, C++
| recently added concepts, and most (modern) languages have
| some way to describe interfaces.
|
| As to your counterexample, I agree with current JavaScript
| that would be a problem, but with good language support it
| would certainly be possible. For example, Rust (and C++?)
| have competing implementations of the global allocator, and
| must users will never notice.
| zdragnar wrote:
| The reactivity layers are all pretty tied into the hearts
| of the frameworks. There's no advantage to any framework to
| expose such a thing to end users to leverage a competing
| implementation.
|
| As for polymorphism, even the current class syntax largely
| operates in the same way as the original prototypal
| inheritance mechanism, with a few exceptions in constructor
| behavior to support subclassing certain built-in objects.
|
| You can pretty easily create run-time traits- like
| functions with prototpyes, the class construct is an
| expression whose resulting value can be passed around,
| mutated, etc.
|
| For example, you can write a function that takes a class as
| an argument, and returns a new class that extends it.
| polynomial wrote:
| Isn't this similar to small core vs large core debates you see
| in other projects, often where drivers are concerned? (ie
| question around where the coupling layer is materialized)
| __s wrote:
| Promises are a nice success story, but without async/await it
| wasn't really necessary to standardize
|
| > The current draft is based on design input from the
| authors/maintainers of Angular, Bubble, Ember, FAST, MobX,
| Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz, and
| more...
|
| Would be interested what existing library authors think of this
| proposal. Interesting that React is not in that list
|
| Signals are a bit like channels, except they're broadcast instead
| of single receiver. It'd be neat if this could somehow be
| leveraged to allow web workers to communicate with channels
| instead of onMessage callbacks. Specifically being able to
| `select` over signals/channels/promises like in Go would over a
| syntactic benefit over having to try manage multiple concurrent
| messaging mechanism with callbacks (maybe by allowing signals to
| be included in `Promise.any`)
| watson wrote:
| > Promises are a nice success story, but without async/await it
| wasn't really necessary to standardize
|
| One benefit of standardisation that's not tied to async/await
| is that the JavaScript engines has been able to do performance
| optimisations not otherwise possible which benefit Promise-
| heavy applications
| KRAKRISMOTT wrote:
| Maybe they can call it "EventEmitter"
|
| https://nodejs.org/en/learn/asynchronous-work/the-nodejs-eve...
| __s wrote:
| That looks like it fits with the theme, but events are
| difficult to compose & tend to have easy leaks by requiring
| explicit attach/detach (not that I'm sure the proposal here
| addresses those issues)
| pavlov wrote:
| _> "Interesting that React is not in that list"_
|
| Signals are not a part of the core React API, unlike Preact.
|
| My vague gut feeling is that signals are too much like a
| generalized useEffect() and would only introduce further
| confusion into React by muddling what happens during the render
| cycle. For better and worse, React takes a different tack to
| updates than signals do. But maybe I'm wrong about their
| applicability.
| whizzter wrote:
| My feeling is that it's philosophically outside the purview
| of React whose focus is rendering (and components and their
| state but not global state), RxJS and and MobX are both
| usable with React and have signals whilst Redux goes another
| route and React is "above" that choice.
| Fatalist_ma wrote:
| There is an interesting debate about React and signals in the
| comments of this article, between Dan Abramov and Ryan
| Carniato - https://dev.to/this-is-learning/react-vs-
| signals-10-years-la...
| pavlov wrote:
| I read it until the point where he defends the idea that
| these two functions obviously do something completely
| different: function One(props) {
| const doubleCount = props.count * 2; return
| <div>Count: {doubleCount}</div>; }
| function Two(props) { return <div>Count:
| {props.count * 2}</div>; }
|
| It honestly made me wonder whether the article was dated
| April 1 and I'd been had.
|
| More generously, JS framework design is hard. If you're
| ambitious at all, you end up fighting the language and your
| runtime paradigms will hang like ill-fitting clothes on its
| syntax. The One/Two example above shows how easily
| expectations break in this world of extensions to
| extensions. There's no way to know what an apparently
| simple piece of code will actually do without knowing the
| specifics of a given framework.
| dclowd9901 wrote:
| React isn't in this list because its effects are declarative,
| not imperative (except for props changes and re-renders which
| you could argue are in fact declarative, just one abstraction
| removed). UseEffect neatly compartmentalizes imperative
| behavior.
|
| This looks a lot like ember data binding which becomes an
| imperative nightmare. Its default state is "foot gun" with tons
| of cognitive overhead and meta patterns to keep it from getting
| that way.
| bastawhiz wrote:
| > without async/await it wasn't really necessary to standardize
|
| Hard disagree.
|
| `x instanceof Promise` simply doesn't work. If my library has a
| then method that accepts a catch callback and yours doesn't,
| they're silently non-interoperable, and there's no way to
| detect it. When does `finally` run? What expectations can you
| have around how async the callbacks are? Without a standard,
| every single library that uses promises needs to bring its own
| polyfill because you can't trust what's there. And you can't
| actually consume any other library's promises, because you
| can't trust that they behave in the way you expect them to.
|
| And I'm not just speculating, this was reality for many years
| and a hell that many of us had to endure.
| lambdaba wrote:
| This looks very much like mobx, which is my favorite JS effect
| system.
|
| Here is the mobx version: import { observable,
| computed, autorun } from 'mobx'; const counter =
| observable.box(0); const isEven = computed(() =>
| (counter.get() & 1) === 0); const parity = computed(()
| => isEven.get() ? "even" : "odd"); autorun(() => {
| element.innerText = parity.get(); }); //
| Simulate external updates to counter... setInterval(() =>
| counter.set(counter.get() + 1), 1000);
| kabes wrote:
| Well, mobx is signals. But signals where the dependencies are
| tracked implicitly via the proxy ovject, instead of explicitly
| by a getter.
| alserio wrote:
| really looking forward to get nice dev tools out of this work
| ivanjermakov wrote:
| Is dependency tracking problem statically solvable (without
| calling effect once and subscribing to all .get's)?
|
| I don't think this needs to be a language feature, rather
| abstraction of existing features. In other words, can't this be a
| library?
|
| I know that SolidJS is able to figure out dependent signals, but
| probably doing so on the first execution.
| ivan_gammel wrote:
| >In other words, can't this be a library?
|
| It is explicitly mentioned in the proposal. The problem with
| 3rd party libraries is their interoperability and diversity of
| implementations, which might be unnecessary for this kind of
| things.
| twiss wrote:
| I believe Svelte does figure it out at build time.
|
| Which does make me question the mention of Svelte in the
| proposal, and makes me wonder what the Svelte developers think
| of it - because IIUC they indeed don't need this (at runtime),
| if I'm not mistaken.
| Phillippe wrote:
| The current Svelte version does it at build/compile time. The
| up and coming Svelte 5 is using signals and the reactivity is
| moved to runtime
| bastawhiz wrote:
| > In other words, can't this be a library?
|
| You answered your own question:
|
| > I know that SolidJS is able to
|
| It already is, obviously. But how is SolidJS supposed to work
| with other non-SolidJS code? It can't. Unless every library
| builds support for every other library, they can't possibly
| interoperate.
| ivanjermakov wrote:
| It's a shame that JS ecosystem is so sparse.
|
| E.g. in Rust it's much more common to rely on existing
| "building blocks" like futures, tokio, syn, serde even just
| for basic bindings, favoring interoperability.
| mg wrote:
| When I need to signal something across my application, I use
| events: window.dispatchEvent(new
| Event('counterChange'));
|
| And every part of the application that wants to react to it can
| subscribe via
| window.addEventListener('counterChange', () => { ...
| do something ... });
|
| Anything wrong with that?
| hk__2 wrote:
| > Anything wrong with that?
|
| It has all the downsides of the pub/sub architecture
| highlighted in the proposal.
| ivan_gammel wrote:
| Did you read the document? They have an example there, which is
| quite similar to yours, and explain what is the problem.
| explaininjs wrote:
| No, they don't. In fact, if you search "event" in the
| proposal you get exactly one result, the prefix of
| "eventually". This is a serious shortcoming of the proposal
| that should be addressed.
| throwitaway1123 wrote:
| I think the comment you're replying to is referring to the
| pub/sub sections of the proposal. They don't explicitly
| mention events, but events are a subset of the
| publish/subscribe pattern.
| explaininjs wrote:
| But then so are signals.
|
| The only "benefit" signals as proposed here give you is
| less control over the exact dispatch pattern of the
| graph, for instance things like debouncing, throttling,
| batching, etc etc etc. Aka all the things you absolutely
| must have control over if you want to make something
| resembling a high performance application.
| naasking wrote:
| Signals are not a subset of events. Signals combine event
| subscription with value construction, which promotes a
| remote declarative model for updates.
|
| > The only "benefit" signals as proposed here give you is
| less control over the exact dispatch pattern of the
| graph, for instance things like debouncing, throttling,
| batching,
|
| Events don't give you any more control over those
| properties than signals, they just require more
| boilerplate.
| ivan_gammel wrote:
| Oh, come on. They do have an example of architecture, not
| literal example of events. It doesn't matter what trivial
| implementation of Observer pattern you choose.
| explaininjs wrote:
| Except one is already built into the language? And it
| provides all the upsides of signals with none of the
| downsides? (Modulo proper use of `using` directives)
| ahmedfromtunis wrote:
| Can you please point to where in the document such an example
| was mentioned? I rechecked the document and couldn't find it.
| ivan_gammel wrote:
| Just look for the example of a design pattern (,,Example -
| A VanillaJS Counter" section), not for a literal
| implementation of it via events. Conceptually they are the
| same.
| djbusby wrote:
| I've been using patterns like this for more than a decade. The
| thing that's hard is, down the road you could have a listener,
| which triggers a other event, then another event, which comes
| back to the first routine and now you got a listen-loop that
| won't quit.
|
| And it's hard to ensure that all listener don't cause that
| trigger cascade.
| eddd-ddde wrote:
| Isn't it possible to define all listeners / publishers in a
| declarative way which can be compiled to catch for this
| issues?
| berkes wrote:
| Yes. It is. And most pub/sub or Observer architectures and
| design patterns have the "loop" thing solved just fine.
|
| In fact, the GOF spend an entire paragraph on the problem
| of complex update semantics in "design Patterns" (1995) ch
| Observer p299.
|
| So, while it is a real problem, it's one that has been
| solved (for at least 29 years)
| cyanydeez wrote:
| Mostly the teardown logic and inevitable memory leaks.
|
| A alt proposal would be some kind of auto remove listener if it
| goes out of context
| explaininjs wrote:
| The new `using` feature handles RAII just fine. This proposal
| is entirely unnecessary given the existence of `using` and
| the already-existing event listeners.
| https://iliazeus.github.io/articles/js-explicit-resource-
| man...
| lelandfe wrote:
| Quite a good link, thanks.
| gray_-_wolf wrote:
| Maybe a stupid question, but isn't the memory released anyway
| when I close the tab? So why do memory leaks matter?
| dr_kiszonka wrote:
| Not a web dev person, but some "tabs" have a very long
| lifetime, e.g., webmail clients, Whatsapp, etc.
| leptons wrote:
| The problem is if you don't close the tab, the memory leak
| can cause that one tab's memory to balloon to 1GB or more
| because memory isn't being released, when that tab should
| only be consuming 50MB.
| zdragnar wrote:
| It depends on how much you're leaking, and how fast.
|
| Best case scenario, it just slows down garage collection a
| little bit, as you're holding into a lot of references that
| aren't going anywhere.
|
| On the other hand, I recall a bug in a particular version
| of AngularJS where component DOM nodes wouldn't get cleaned
| up when navigating with the router unless you manually set
| all of the scope values their templates used to null.
|
| We had a data dense application with tables and what not,
| and you could clearly watch memory jump tens or more
| megabytes flipping between pages in the chrome dev tooling.
|
| Eventually (this was a SPA intended to be used for upwards
| of hours at a time) the page would crash.
| Xenoamorphous wrote:
| Have you ever seen Chrome's "aw, snap!" screen (if you use
| Chrome)?
|
| More often than not that's due to a memory leak.
| explaininjs wrote:
| This pattern is precisely what everyone's favorite "look,
| JS/electron can be high performance!" example uses. (VS Code).
| zdragnar wrote:
| According to TFA, event emitters / observables cause
| unnecessary work when called multiple times.
|
| The difference with signals is that the resulting value is only
| ever calculated when the end consumer reads the value- so you
| schedule render updates asynchronously from the actual writes
| to the signal, and whatever chain of computations the watchers
| perform is done just the one time during the render.
|
| Interim values sent to the signal will get lost, so you really
| can't do too much interesting work in them. It's really just a
| fancy abstraction layer to coordinate a rendering cycle.
| _the_inflator wrote:
| Historically, this example is the reason why the Web evolved
| into jQuery and from there forked into the world of Angular and
| React mainly.
|
| Event handling is getting messy very easily. If you want to get
| deeper into it, have a look at event bubbling and propagation.
|
| Large applications need a robust event handling. This is the
| nowadays hidden benefit of frameworks like Angular, Vue etc.
|
| Believe me, you don't want to use the standard event handling
| API without a framework. Adding, deleting, cloning, firing,
| removing, fire once etc on many elements can have serious
| unwanted side effects.
| beezlewax wrote:
| What kind of side effects specifically?
| doomroot wrote:
| I believe memory leaks to start
| falcor84 wrote:
| Are you implying that there are memory leaks in browsers'
| internal implementation of events? Because my take is
| that the problem is with "user space" scripts not
| cleaning up after themselves, and I don't see how that
| would get better by adding yet another API to be mindful
| of.
| gibbitz wrote:
| I believe this would be more related to something like
| memoizing a DOM structure in a "Live" listener that is
| later removed from the DOM but not garbage collected due
| to the reference in the event listener. As the poster
| mentioned, developer error -- not a fundamental language
| or browser implementation flaw.
| Klonoar wrote:
| This is one of those points where you need to link to
| some kind of data or conclusive result actually
| showcasing this, because you're arguing against a pattern
| that's been in browser ecosystems for decades. I've done
| this for larger applications and haven't experienced
| issues.
|
| Being charitable, the best I can imagine right now that'd
| cause memory leaks is someone running into the old school
| JS scoping issues and capturing something in handlers
| that they shouldn't. That's not the handler itself that's
| the problem, though - that's the developer.
|
| (Yes, we could rant on and on about the poor design
| decisions that JS has built in, but that's been beaten to
| death)
| unemployable wrote:
| Memory leaks can occur when a component adds events to an
| element outside of the component (such as the window) and
| then gets removed from the DOM without removing the event
| handler from the window. This is solved in native Web
| Components by the mount/unmount methods where you can run
| code to remove event listeners when the component has
| been unmounted.
|
| For other event listeners, they get removed when the DOM
| element is removed.
|
| The frameworks do not solve this to any greater degree.
| They also just make everything invisible and behind-the-
| scenes and hard to debug due to their declarative nature,
| but that is another topic.
| andrewstuart wrote:
| I use events extensively in large applications and its never
| been a problem. In fact they solve complexity.
| unemployable wrote:
| >If you want to get deeper into it, have a look at event
| bubbling and propagation.
|
| In this example, the event is on the Window. There is no
| bubbling. It is already at the top level.
|
| >Believe me, you don't want to use the standard event
| handling API without a framework. Adding, deleting, cloning,
| firing, removing, fire once etc on many elements can have
| serious unwanted side effects.
|
| I don't know what this means. The frameworks do not have much
| to do with this topic.
| nurple wrote:
| Welcome to Node.js v21.6.2. Type ".help" for more
| information. > window.dispatchEvent(new
| Event('counterChange')) Uncaught ReferenceError: window
| is not defined
| runarberg wrote:
| $ node Welcome to Node.js v20.6.1. Type
| ".help" for more information. > const target = new
| EventTarget() undefined >
| target.dispatchEvent(new Event("counterChange")) true
| nurple wrote:
| tyvm!
| lelanthran wrote:
| > Anything wrong with that?
|
| Well, don't events only bubble upwards? You need to know the
| exact element of it is not on a lower level in the DOM tree.
|
| Events were too messy, so I wrote a small pub/sub message queue
| type of thing. Anyone anywhere in the DOM can subscribe to
| messages based on subject regexes.
|
| Makes things a lot easier, especially when I added web
| components to wrap existing elements so that publishing and
| subscribing is done with attributes, not js.
| everybackdoor wrote:
| You can also fire events on pretty much any object,
| essentially creating a channel where the message bus queue is
| the vm event queue.
| dakom wrote:
| It's often desirable for UI to be described in a declarative
| fashion, i.e. instead of where you have "do something" (set
| button color to red), refactoring so it becomes "is something"
| (button is red if state is x)
|
| I might not be describing that well, because once you go down
| that road it really becomes a whole overall approach that
| infects the whole program (like functional reactive
| programming), and so it's really about how the whole flow fits
| together from top to bottom, and that can be very elegant.
|
| I don't think that's the right fit for everything, i.e. in
| gamedev it might make more sense to just update some object's
| position imperatively, but for UI it tends to work pretty well.
| LittleDan wrote:
| LGTM! Smells like Redux (in a good way). But then ultimately at
| the root you probably want the event to update your "model",
| and then that leads to an update of the "view". This is the
| part where signals can be useful.
| briantakita wrote:
| > Anything wrong with that?
|
| It only works in browser environments.
| lelandfe wrote:
| Node has EventEmitter:
| https://nodejs.org/en/learn/asynchronous-work/the-nodejs-
| eve...
|
| See, for instance,
| https://www.electronjs.org/docs/latest/api/ipc-renderer
| kabes wrote:
| Signals are also just pub/sub, but with a more ergonomic api.
| More ergonomic because listeners are added and released
| automatically.
|
| It can also be more performant, eg, say you have a computation
| that depends on 2 values:
|
| `result = a ? b : 0`
|
| Then if a is falsy, we don't need to recompute if b changes.
| This is achieved automatically with signals, but would require
| quite some code with classic pub/sub.
| fwlr wrote:
| If you mis-type 'countenChange' it could be quite frustrating!
| everybackdoor wrote:
| In 2024 we have linters and other static analysis tools for
| catching these kinds of things right in the IDE.
| berkes wrote:
| If that truly is your reason to choose or forego a software
| design pattern, than what the h. are you doing with
| JavaScript?
| lloydatkinson wrote:
| Sorry but I'll be a little bit mad if this Preact/Angular
| inspired thing is approved and made part of the spec, while
| Observable was deliberately ostracised.
| troupo wrote:
| > I'll be a little bit mad if this Preact/Angular inspired
| thing
|
| Signals predate both and originate in KnockoutJS at least. They
| were popularized in recent years by SolidJS. And then adopted
| into Preact, Vue and others. Angular is a very late newcomer to
| the signals game.
|
| Edit: and this is literally in the introduction section:
|
| --- start quote ---
|
| This first-class reactive value approach seems to have made its
| first popular appearance in open-source JavaScript web
| frameworks with Knockout in 2010. In the years since, many
| variations and implementations have been created. Within the
| last 3-4 years, the Signal primitive and related approaches
| have gained further traction, with nearly every modern
| JavaScript library or framework having something similar, under
| one name or another.
|
| --- end quote ---
|
| The list of libraries in the README is alphabetized, and
| doesn't reflect the evolution of signals in the frameworks and
| libraries.
| ivan_gammel wrote:
| I like the quality of this proposal, which reminds me of JSRs. It
| does make a lot of sense.
| andrewstuart wrote:
| What does this buy me over events?
| troupo wrote:
| Automatic dependency tracking, guarantees against circular
| references, improved observability and potentially better
| devtools
| everybackdoor wrote:
| And a backdoor in a build tree
| andrewstuart wrote:
| That's not enough to compete with an existing "good enough"
| solution.
| troupo wrote:
| There's no existing "good enough" solution for reactive
| values in JS.
|
| Also https://news.ycombinator.com/item?id=39887187
| claytongulick wrote:
| Getters and setters work pretty well.
| addEventListener("foo", () => {...}, {once: true})
|
| Is a pretty easy way of handling one-shot events.
|
| Those have been "good enough" for me to build large,
| complex healthcare applications.
| makkesk8 wrote:
| Looks useful, but what baffles me is.. Why is every framework
| setting state or their "signals" using "setX" functions? What's
| wrong with the built in getter and setters that you can either
| proxy or straight up override?
|
| This feels arguably cleaner: something = "else";
|
| Than: setSomething("else");
| j1elo wrote:
| For one I can write ".set" and the IDE would auto-complete with
| all possible somethings that can be set, even without having
| the slightest idea of which ones there are.
|
| I've very much enjoyed this kind of consistency wherever is
| found (having a common prefix for common behaviors, in this
| case, setters)
| dclowd9901 wrote:
| One big problem is right now they are _tremendously_ slow to
| use. (At least through the Proxy native class). Not sure if
| this is an artifact of JITs or the nature of prototypal
| inheritance.
| notnullorvoid wrote:
| Some libraries that feature signals style reactivity do use
| getter and setters (ember.js, and mobx are 2 good examples).
| However it makes sense for the primitive API to use functions
| since getter and setters are functions under the hood and get
| applied to an object as part of a property descriptor. It's
| also not always desirable to have a reactive value embedded in
| an object, sometimes you just want to pass around a single
| changeable value.
|
| As for why some libraries choose the `[thing, setThing] =
| signal()` API (like solid.js) that's often referred to as read
| write segregation. Which essentially encourages the practice of
| passing read only values by default, and opting in to allowing
| consumer writes on a value by explicitly passing it's setter
| function. This is something that was popularized by React
| hooks.
|
| Either way this proposal isn't limiting the API choice of
| libraries since you can create whatever kind of wrappers around
| the primitive that you want.
| chuckadams wrote:
| well to use setters it has to be "foo.something = else",
| because JS can't override plain old local bindings -- not since
| "with" was sent to the cornfield anyway. Once you do that, you
| can indeed have a framework that generates getters and setters,
| which is exactly what Vue 2 does. Switch to proxies instead of
| get/set and you have Vue 3 -- the signals API is pretty much
| identical to the Vue composition API.
| everybackdoor wrote:
| - They're saying " _the community_ wants less boilerplate"
|
| - They introduce what is effectively a black-box system
|
| - The new system is expected to handle all application state
|
| - They try to push it for frontend folks while also remarking
| that it would be useful for _build systems_
|
| This has the same red flags the xz saga had.
|
| Have we learnt nothing.
|
| Lots of "users" here vouching for the pattern and hoping it gets
| adopted. I bet this gets some nice damage control replies because
| there's social engineering going on here right now and most seem
| to not be aware of it.
| londons_explore wrote:
| Can't we just call javascript 'done'?
|
| We keep adding things to the language, and never subtract
| anything, which means learning it as a language is getting harder
| and harder.
| troupo wrote:
| They don't add these to the language. They add these to the
| (currently basically non-existent) standard library.
|
| Given that almost every single framework under the sun (except
| React) has converged on signals, it makes sense to move that
| into the browser. This... this is how the web is supposed to
| work.
| Waterluvian wrote:
| I dunno. I sometimes feel the same, but a whole ton of recent
| features have been incredible at cleaning up code.
|
| ?? Is my favourite.
|
| I also want set functions and possibly a match statement
| thingy.
| angleofrepose wrote:
| Off topic, but I'm wondering if anyone attracted to this topic
| could help me understand why JavaScript doesn't have macros.
|
| I'm aware of much conversation around dismissing macros, often in
| the context of bad dev experience -- but this sounds like a
| shallow dismissal to me.
|
| At the end of the day, we have some of the results of macros in
| the JavaScript ecosystem, but rather than being supported by the
| language they are kicked out to transpilers and compilers.
|
| Can anyone point me to authoritative sources discussing macros in
| JavaScript? I have a hard time finding deep and earnest
| discussion around macros by searching myself.
| hoten wrote:
| You could start here:
| https://github.com/search?q=org%3Atc39%20macro&type=code
| angleofrepose wrote:
| A great resource that I should have found on my own. Thank
| you. I'll look through this later. Giving it a quick glance
| now I see some of the same language I see other places; here
| that macros are "too far."
|
| I don't know why macros are approached with apprehension. As
| I briefly get at in my first comment, I'm aware of a lot of
| dismissals of macros as a tool, but those dismissals don't
| make sense to me in context. I'm missing some backstory or
| critical mind-share tipping points in the history of the
| concept.
|
| What could be a good set of sources to understand the
| background perspective with which TC39 members approach the
| concept of macros?
| notnullorvoid wrote:
| Macros don't really make sense in JS runtime spec. Since you
| can mostly already achieve macro level features by using eval
| or new Function, but it's not very efficient. Macros make most
| sense at build time, and there have been a few attempts at
| generalized build macros with various bundlers / transpiler
| plugins. I think the space needs more time to mature. I'm
| optimistic that we'll eventually see some sort of (un)official
| macro spec emerge.
| bastawhiz wrote:
| Interpreted languages rarely have macros.
|
| But more importantly, do you really want script tags on
| webpages defining macros that globally affect how other files
| are parsed/interpreted? What if the macro references an
| identifier that's not global? What if I define a macro in a
| script that loads after some other JavaScript has already run?
| Do macros affect eval() and the output of
| Function.prototype.toString?
|
| Sure, you could scope macros to one script/module to prevent
| code from blowing up left and right, but now you need to repeat
| your macro definitions in every file you create. You could
| avoid that by bundling your js into one file, but now you're
| back to using a compiler, which makes the whole thing moot.
| calebpeterson wrote:
| Not a bad thing at all... but this is the same mental model
| provided by the so-called atom-based state management systems in
| React. I believe Jotai is the most popular.
| kookamamie wrote:
| Looks bad. It adds what looks like more nonsense complexity.
| Also, "Signals" as a name is not descriptive to what is proposed
| (see e.g. Qt Signals).
| tqwhite wrote:
| Yes. Thank you. I am already tired trying to figure out how
| this would improve my life and annoyed at trying to read other
| people's code that has more abstracted crap in it.
| notnullorvoid wrote:
| I do personally think signal is a bit of a poor naming choice,
| but it has become probably the most recognizable term used for
| the concept in the JS ecosystem. There's not a whole lot of
| short concise naming options either, but maybe "Reactive
| Values" is better?
| dclowd9901 wrote:
| Cool. My least favorite, cumbersome and brittle aspect of using
| Ember becoming a core aspect of JavaScript functionality.
|
| No fucking thank you.
| rblatz wrote:
| The lack of signals isn't remotely the largest issue with JS, and
| adding them has minimal impact for most users of JavaScript. The
| biggest issue is the lack of a standard library, resulting in npm
| hell in most projects.
| phpnode wrote:
| JS does have a standard library and this proposal is about
| expanding it, so that's good, right?
| mrpepka wrote:
| Unless it's half-assed, clumsy and short-sighted, and we are
| stuck with it forever, because standard. Until one's
| implementation becomes a standard thanks to the fact it wins
| as a library (as jQuery did), it should not be slyly forced
| into the language.
| alexchamberlain wrote:
| This is referenced in the proposal:
|
| > JavaScript has had a fairly minimal standard library, but a
| trend in TC39 has been to make JS more of a "batteries-
| included" language, with a high-quality, built-in set of
| functionality available
|
| I think the description "minimal" is fairer than "no" wrt the
| standard library.
| preommr wrote:
| Strongly disagree - it's trivial for a project to just add a
| small std-lib, or add lodash as a single dep, or just add it
| directly as source code.
|
| JS projects exist in npm hell because people have been taught
| to use a library to save typing 10 characters. No standard
| library is going to fix. Because someone can just call in a new
| lib that just curries something in the standard lib with minor
| improvements like caching.
| tambourine_man wrote:
| I've been trying for decades to understand why people find it so
| hard to keep track of state and update the DOM.
|
| Sure, it requires a bit of discipline, but it's vastly simpler to
| me than whatever solution comes up every few years (Backbone,
| Knockout, Angular, React, modifying the language itself, etc).
| There must be something profoundly different with the way I
| think.
|
| It even expresses itself in the function naming. They call
| updating innerText "render". You're not rendering anything. At
| most, the browser is, but so is everything else it does related
| to painting. It feels like a desperate attempt to complicate what
| is one of the simplest DOM functions. It really baffles me.
| duxup wrote:
| In simple applications it is easy.
|
| More complex it is not easy.
| tambourine_man wrote:
| I've been writing web apps for easily 25+ years. Never have I
| reached for React and friends voluntarily. But again, I know
| I'm in a minority. I'm just not completely sure why.
| duxup wrote:
| You could work for 50 years and never need those... it just
| depends on what you're creating / how many devs and so on.
|
| I know some guys who over the years wrote their own
| framework. It works great... for them.
| tambourine_man wrote:
| Right, the only thing that convinces me is team and
| hiring dynamics. But that's not what these tools
| advertise.
|
| It's always like: you have dozens of interactive controls
| in this view, it's getting out of hand, you should use
| this language that compiles to HTML and JavaScript and
| carry all these dependencies. To which I always reply: no
| thanks, I rather deal with the dozens of controls.
| duxup wrote:
| > team and hiring dynamics. But that's not what these
| tools advertise.
|
| I think that's a given for any proposed standard. We all
| get a common way we understand things and can even just
| communicate about a thing.
| tambourine_man wrote:
| My argument is that it's not their selling point. When
| you go to React's page you're not greeted with: "React is
| a great way to hire devs and manage a team".
|
| The solution they sell is technical, like state
| management, reusable components, etc. Which I don't find
| convincing.
| pvg wrote:
| FB's website has settings subpages that are more
| complicated than an average web app and is an SPA-style-
| mega-app made of piles of other apps. It typically keeps
| highly consistent state throughout. There's no doing that
| sanely by hand, you'll just forget something or, more
| likely, it will get lost between the dozens upon dozens of
| people needed to build such a thing.
| tambourine_man wrote:
| 1) Almost no one is building FB or Gmail, yet act like
| it. The precise reason why still escapes me.
|
| 2) For other use cases, it's not that hard to manually
| update some elements in the DOM. You very quickly learn
| how not shot yourself in the foot. Certainly a lot easier
| (and faster) than dealing with the mess that is the React
| ecosystem.
| mardifoufs wrote:
| You don't need to be building Facebook or Google to have
| a TON of state in a webpage. Obviously if you are doing a
| basic dashboard, a blog or something similar, it isn't
| really needed. But for more complex stuff I think react
| provides a much better way to handle state changes than
| just using vanilla js. It's not like using react makes an
| app more complex, because if you wanted to do the same
| thing in vanillajs you'd end up with a much bigger mess.
|
| I agree that some web pages don't need any of that, but
| those don't usually require a lot of development anyways.
| __david__ wrote:
| Do you just use document.createElement(), getElementById(),
| and friends? I've written lots of web apps that just used
| those. But I've also jumped into React a couple times to
| learn if it's better or faster or whatever. I generally
| think it's a reasonable approach for a template style web
| app--ie, when you need to build a whole lot of html nodes
| that interact with each other in the way a complicated ui
| does. But I do find I can get stuck trying to reason about
| some of the weird reactivity stuff with useState() and
| useEffect(). I kinda chalk that up to me being not an
| expert, but it also feels like a bit of an impedance
| mismatch with the language itself.
|
| But I don't think React necessary for _every_ app and it
| really depends on what kind of apps you are making.
|
| Certainly you can do the original style of app where the
| templating is on the server and any js is just to hook up
| already existing nodes. The js community has more or less
| moved away from that "rails" style of app years ago...
| dclowd9901 wrote:
| Decades is a long time. You must remember the complexities of
| updating the DOM between browsers, surely.
|
| Keeping the DOM in sync with a data state isn't too difficult
| but doing so in a highly performant (60 fps) way _is_
| tremendously difficult. Especially when it comes to creating
| APIs that aren't leaky but also not too cumbersome.
|
| It would frankly be easier to just paint pixels to a canvas
| game-style, than translating changes to a living DOM tree.
| tambourine_man wrote:
| If you need 60fps you shouldn't use the DOM. It's not a game
| engine. Like you said, go with canvas.
| beders wrote:
| I've been enjoying "signals" in the form of re-frame
| subscriptions for many years now.
|
| They solve a neat subset of problems in front-end developments.
| But they don't solve all of them.
|
| Adding it to JavaScript as language construct is unnecessary.
| tommiegannert wrote:
| Hmm, if we can optimize the reactive state management in all web
| apps, that sounds cool.
|
| If this is to be a base for VueJS, it should handle deep changes.
| They have a note about support Map and Set, but being able to to
| control depth is nice in VueJS. (I'd say watch() should be deep
| by default, since non-deep is an optimization that can lead to
| accidental inconsistent state.)
|
| Streams. Generally, I find RxJS backwards. Usually you just need
| "state", so that should be the easiest thing to implement. But I
| can't deny that the programming model is beautiful. Standardizing
| "state" without also considering streams seems odd to me. The
| "computed" creates a pipeline of updates, very similar to one
| you'd do with a map over a stream. If RxJS didn't already exist,
| I probably wouldn't have cared about this duality.
|
| Async. Sure, signals can be synchronous, but Computed should
| definitely play well with async functions. This is a big
| shortcoming in VueJS (that people work around on their own.) That
| also implies handling "pending computation" gracefully for
| debuggability. I see there's a "computing" state, but this would
| have to be surfaced to be able to debug stuck promises.
|
| Exceptions. I like the idea of .get() rethrowing exceptions from
| Computed. VueJS is a bit vague on that front, and just stops.
| nullvoxpopuli wrote:
| > it should handle deep changes.
|
| these can be implemented in userland via proxy -- and I think
| probably should, as is proven by this collection of utils:
| https://twitter.com/nullvoxpopuli/status/1772669749991739788
|
| If we were to try implementing _everything_ as reactive
| versions, there 'd be be no end, and implementations couldn't
| keep up -- by pushing reactive Map/Set/etc to userland/library
| land, we can implement what we need when we need it
| incrementally, built on the solid foundation of the signal
| primitives.
|
| > since non-deep is an optimization that can lead to accidental
| inconsistent state.
|
| conversely, deep-all-the-time is a performance hit that we
| don't want to be default. Svelte and Ember take this approach
| of opt-in-deep reactivity.
| _heimdall wrote:
| I don't think signals will help move development away from
| further complexity, and that's really what we need today.
|
| There's a fundamental question of why modern sites/apps reach for
| patterns like signals, memorization, hybrid rendering patterns,
| etc. I wouldn't begin to claim I have all the answers, but
| clearly there are gaps in the platform with regards to the
| patterns people _want_ to implement and I 'm not sure that
| jumping to signals as a standard helps better understand whether
| its the platform or our mental models that need updating to get
| back in sync.
|
| Personally I've found code much easier to maintain when the
| frontend is only responsible for state that truly is temporary
| and doesn't live on the back end at all. For me any persisted
| state belongs on the server, as does any rendering that depends
| on it. This largely makes signals unnecessary, very few apps have
| such complex temporary state that I need a complicated setup to
| manage it.
| itsjustme2 wrote:
| Am I the only one that thinks the vanilla js example is actually
| easier to read and work with?
|
| - "The setup is noise and boilerplate heavy." Actually the
| signals example looks just as noisy and boilerplate heavy to me.
| And it introduces new boilerplate concepts which are hard for
| beginners to understand.
|
| - "If the counter changes but parity does not (e.g. counter goes
| from 2 to 4), then we do unnecessary computation of the parity
| and unnecessary rendering." - Sounds like they want premature
| memoization.
|
| - "What if another part of our UI just wants to render when the
| counter updates?" Then I agree the strawman example is probably
| not what you want. At that point you might want to handle the
| state using signals, event handling, central state store (e.g.
| redux-like tools), or some other method. I think this is also
| what they meant by "The counter state is tightly coupled to the
| rendering system."? Some of this document feels a little
| repetitive.
|
| - "What if another part of our UI is dependent on isEven or
| parity alone?" Sure, you could change your entire approach
| because of this if that's a really central part of your app, but
| most often it's not. And "The render function, which is only
| dependent on parity must instead "know" that it actually needs to
| subscribe to counter." is often not an unreasonable obligation. I
| mean, that's one of the nice things about pure computed
| functions- it's easy to spot their inputs.
| duxup wrote:
| I agree the initial example is easier to read, but it has
| problems as stated.
| leononame wrote:
| 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?
| 38 wrote:
| > 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
| briantakita wrote:
| Since reactivity is not baked into Javascript. Adding
| reactivity is going to add abstraction overhead. It's meant to
| be used if it's needed. Not necessarily a default way to work
| with state.
|
| In my experience, the big benefit is the ability to make
| reactive state modular. In an imperative style, additional
| state is needed to track changes. Modularity is achieved using
| abstraction. Only use when needed.
|
| > Sounds like they want premature memoization
|
| It's a balance to present a simple example that is applicable.
| Cases where reactivity have a clear benefit tend to be more
| complex examples. Which is more difficult to demonstrate than a
| simple, less applicable example.
| LittleDan wrote:
| I think there is room for improvement in how we explain this.
| The problems aren't really visible in this small sample and
| comes up more for bigger things. PRs welcome.
| briantakita wrote:
| Perhaps mentioning the tradeoffs between a simple easy to
| explain example vs a more obvious comprehensive example. With
| links to more complex code bases? With a before & after?
| ksherlock wrote:
| I agree. But look at Preact's signal documentation -
|
| https://preactjs.com/guide/v10/signals
|
| "In Preact, when a signal is passed down through a tree as
| props or context, we're only passing around references to the
| signal. The signal can be updated without re-rendering any
| components, since components see the signal and not its value.
| This lets us skip all of the expensive rendering work and jump
| immediately to any components in the tree that actually access
| the signal's .value property."
|
| "Signals have a second important characteristic, which is that
| they track when their value is accessed and when it is updated.
| In Preact, accessing a signal's .value property from within a
| component automatically re-renders the component when that
| signal's value changes."
|
| I think it makes a lot more sense in a context like that.
| andrewstuart wrote:
| >> In Preact, when a signal is passed down through a tree as
| props or context,
|
| I have found that passing props makes React-like applications
| very complex and messy and props are to be avoided as must as
| practical.
|
| The mechanism for avoiding props is Custom events.
|
| It concerns me to see the concept of signals being passed as
| props when surely signals/events should be removing the need
| for props?
| vbezhenar wrote:
| I didn't understand the example in the linked README.
| // A library or framework defines effects based on other Signal
| primitives declare function effect(cb: () => void): (() =>
| void);
|
| What library? What framework? I lost here. What's effect?
| effect(() => element.innerText = parity.get());
|
| How does effect knows that it needs to call this lambda whenever
| parity gets changed? Will it call this lambda on any signal
| change? Why this talk about caching then? Probably not.
|
| Anyway I think that signal idea is sound, if I understood
| correctly what the authors tried to convey. My main issue with
| those decoupling architectures is that once your application is
| complex enough, you will get lost trying to figure out why this
| particular event being emitting. Ideally signals should fix this
| by modifying stacktrace, so when my callback is being called,
| it'd already contain a stacktrace of the code which triggered
| that signal in the first place.
| troupo wrote:
| 1. effect is any function you want to invoke
|
| 2. with signals the dependency tracking mechanism knows what
| values need to be recalculated and as a result the system knows
| which functions to call again
| throwitaway1123 wrote:
| > What library? What framework? I lost here. What's effect?
|
| There are various libraries that export a function called
| effect which allows you to run arbitrary code in response to a
| signal update. The Preact docs have a great primer on signals
| and effects: https://preactjs.com/guide/v10/signals#effectfn
|
| As I understand it, these effect functions run the callback
| once initially to see which signals were accessed while the
| callback was executing, and then call the callback again
| whenever the signals it depends on update. As long as signal
| access is synchronous and single-threaded, you know that if a
| signal was accessed during the callback's execution that the
| callback should be subscribed to those signals.
|
| > How does effect knows that it needs to call this lambda
| whenever parity gets changed? Will it call this lambda on any
| signal change?
|
| You can do this with getters [1], where the effect function
| tracks which properties of the signal were accessed in a getter
| method (I believe Vue historically did this in version 2), but
| you can also track object access using proxies [2]. The example
| from the proposal simply has a 'get' method that is called to
| access the value of the signal, and executing this method
| allows dependencies to be tracked.
|
| [1] https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
|
| [2] https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
| pyrolistical wrote:
| I suspect the watcher is needed by the implementation of effect
| mondrian wrote:
| > How does effect knows that it needs to call this lambda
| whenever parity gets changed?
|
| The call `parity.get()` will register a dependency on the
| function that is passed to `effect()`. When `parity` is
| updated, the function is called.
|
| > Will it call this lambda on any signal change?
|
| Only when a dependent signal changes.
|
| In this case, `parity` depends on `isEven` and `isEven` depends
| on `counter`. So when `counter` is updated, that whole
| dependency chain is invalidated leading to `parity` getting
| invalidated, and that callback re-running.
| posix86 wrote:
| This works well until you accidentally have an if/else
| branch, then you get hard to track bugs (halting problem in
| the general case).
|
| I'm guessing this is why they don't propose to add this
| function to the standard: That fact makes it not very pretty.
| mondrian wrote:
| Hmm, can you explain how if/else branches cause hard to
| track bugs?
| __s wrote:
| if/else isn't a problem. If the condition isn't dependent
| then update of unused branch doesn't matter
| jupp0r wrote:
| This is a horrible idea. Instead of building some dependency
| tracking into this niche feature of the language, JS should come
| up with a generic way of enabling framework developers to clean
| up resources without putting the burden on users of their API to
| manually do this.
| henriquez wrote:
| "Lets make my random UI state tracking framework part of the
| JavaScript spec"
| ttfkam wrote:
| Looks like Svelte 5 only somehow worse?
| bastawhiz wrote:
| The proposal is very clear that it's not trying to be pretty,
| it's trying to be sensible and correct so that frameworks like
| Svelte can build on top of them and work interoperably with
| other libraries and frameworks.
| artemonster wrote:
| Easier to port react to vhdl or verilog?
| meindnoch wrote:
| "let's bake my current framework du jour into the standard
| library!"
|
| It's a bit like tattooing your girlfriend's name onto yourself.
| nikeee wrote:
| Back in the days there was an effort to put observables in the
| language because they were popular (and rxjs was, too).
|
| Glad that didn't happen. And I think everybody else is, too.
| Maybe we should keep that in mind when standardizing features of
| frameworks.
| junon wrote:
| Related is S.js: https://github.com/adamhaile/s
|
| I love signals. I prefer them when making UIs over any other
| primitive (besides, perhaps, the cassowary constraint algorithm).
| I try to replicate them in every language I use, just for fun.
|
| I also don't believe they belong in the Javascript language
| whatsoever. Let the language be for a while, people already
| struggle to keep up with it. TC-39 is already scaring away people
| from the language.
| da39a3ee wrote:
| This article is missing a "What are signals" section. And yes,
| this does not do the job:
|
| > Within JS frameworks and libraries, there has been a large
| amount of experimentation across different ways to represent this
| binding, and experience has shown the power of one-way data flow
| in conjunction with a first-class data type representing a cell
| of state or computation derived from other data, now often called
| "Signals".
| readline_prompt wrote:
| Curious, what about using Proxies for handling state?
| CanaryLayout wrote:
| Oh God why.
___________________________________________________________________
(page generated 2024-03-31 23:00 UTC)