7 ms·
Managing State with Signals
- bradrn 3y agoThe previous article in the series is also a very interesting read: https://tonsky.me/blog/humble-state/ https://tonsky.me/blog/humble-state/
- quechimba 3y agoThanks for the link. I really enjoyed reading both
- samsquire 3y agoIt's interesting how every build system, frontend framework, programming language implements its own promise pipeline/delayed execution/observables/event propagation. But the implementations are rarely extracted out for general purpose usage and rarely have a rich API. I've been thinking a lot about a general purpose "epoll" which be registered on objects that change. I want to be able to register a reaction to a sequence of actions on arbitrary objects with an epoll style API. One of my ideas is GUI thunking. The idea that every interaction with the GUI raises a new type that can be interacted with, to queue up behaviours on the GUI. This is essentially Future<> that are typed and the system reacts to the new type based on what you did and presents a GUI that is as if the operation you queued up was completed. (You can interact with the future because any action on something that isn't done yet, is queued up) It's a bit like terraform plan and apply, but applied to general purpose GUIs. For example, you can click download file, then queue up installation and then using the application, ALL BEFORE it is installed. Because the actual computation is separate from the type information that was queued up. Imagine using AWS Console to set up an entire infrastructure and wire everything together but not actually execute anything until the very end when you click "Run". https://github.com/samsquire/gui-thunks https://github.com/samsquire/gui-thunks I feel we are still early days with regard how to build computer user interfaces that are easy to build, maintain and understand the code for. I used knockout and angularjs 1 and I enjoyed Knockout especially. ko.observables and the map plugin makes creating reactive code very straightforward.
- still_grokking 3y agoThis sounds almost like you've just invented Haskell-like `IO`. All operations get "queued up" in IO and only run "at the end of the world". To write your program you `flatMap` over the `IO`: An `IO` contains the computation(s) that will run at some point, but you can map on the result of them right away, and return another `IO` value; than `flatten` the `IO[IO]` data structure (which makes `flatMap`). In Haskell you have extra syntax for `flatMap` called "do notation". In Scala, where there are library solutions for `IO`, you can use "for comprehensions" instead. In both cases the nested `flatMap` calls get sequenced. This way and you can write code almost like a consecutive chain of imperative procedure calls but it all gets "queued up" and the whole program only runs when the `IO` data structure gets evaluated by the runtime ("at the end of the world", as last call in your program). But that's not really related to data flow.
- threatofrain 3y ago> It's interesting how every build system, frontend framework, programming language implements its own promise pipeline/delayed execution/observables/event propagation. Angular relied on RxJS.
- spiralx 3y agoAnd RxJS is itself an implementation of the language-agnostic Reactive framework.
- bsaul 3y ago"It's interesting how every build system, frontend framework, programming language implements its own promise pipeline/delayed execution/observables/event propagation." This rings so true to me. I've recently realized how every single non trivial part of my app is in fact a workflow problem : it could be ideally written as a pipe of asynchronous steps, glued together. It's true both for the frontend part and the backend. I believe that's the point of reactive frameworks, but somehow those frameworks are usually designed around continuous streams of incoming events. Which isn't what i've noticed is the most widespread case. One-shot instanciation of pre-designed workflows would be really ideal.
- lewiscarson 3y agoThis looks really interesting but the colour scheme makes it impossible to read.
- low_tech_punk 3y agoHave you tried the dark mode? I thought my browser was broken when I turned it on.
- red_trumpet 3y agoWow, truly night mode. Hilarious!
- jackdh 3y agoShould really call it spotlight mode! On a side note, if you do want a normal darkmode, the Darkreader extension works very well including on this site.
- can16358p 3y agoToo much skeuomorphism at its finest: real "dark" mode that it simulates pointing a flashlight.
- Syntonicles 3y agoIt's one of the first times I've been delighted by an unexpected UI in years. We've all settled on conventions in a world where anything is possible.
- phforms 3y agoI actually found it surprisingly comfortable to read, but of course, everyone is different. You can always use the browser console to disable/change the `background-color` (in this case also `background-image`) CSS property of the body.
- Zecc 3y agoThe fact this comes from the same author as https://grumpy.website/ https://grumpy.website/ makes it extra hilarious.
- mpweiher 3y agoIt looks like we are going to keep reinventing dataflow constraints[1] over and over again, always with slightly different terminology (Rx, FRP, signals, ...) So it (a) appears to be a very useful or at least attractive concept, and (b) somehow difficult to fit into current programming languages/practice in a clean way. [1] https://blog.metaobject.com/2014/03/the-siren-call-of-kvo-and-cocoa-bindings.html https://blog.metaobject.com/2014/03/the-siren-call-of-kvo-an... (HN: https://news.ycombinator.com/item?id=7404149 https://news.ycombinator.com/item?id=7404149 )
- klabb3 3y agoI feel like reactive programming is approaching ripeness for a mainstream programming language or cross-stack paradigm within the next few years. React, Svelte, Redux etc in the frontend world has certainly paved the way on the mainstream side, albeit in a simplified environment (singlethreaded, does not cross the network boundary and can simply share memory cheaply). I wonder if a refined version of this with will prevail, or something else like Rx? What are the primitive operations in such a model? Streams, futures, “watch” APIs?
- agumonkey 3y agoI'd go even one step further, diffusion/chemical/tissue-level programming. I'm sense the idea of multi agent state morphism becoming a norm in people's mind. (from css to to react to kubernetes).
- dgellow 3y agoReactiveUI is based on Rx and is very popular in the .Net world: https://www.reactiveui.net/ https://www.reactiveui.net/.
- bheadmaster 3y agoWhat's the difference between reactive and functional programming? Both seem to focus on expressing relationships between objects and having compiler/runtime infer the computation, instead of explicitly specifying the computation.
- inglor 3y agoThis is what MobX does and so does Vue and a few other frameworks like Solid or Qwik (though with a different rendering model) - hardly a novel idea. Heck even in lisps there was reagent which was basically this and had atoms/signals :)
- codeulike 3y agoThat site really shows the best way to do dark mode
- quickthrower2 3y agoIt is a fuck-you dark mode
- jlbooker 3y agoThe yellow background is a bit abrasive, so I was happy to see a dark mode switch.. but yea, fuck me.
- hoosieree 3y agoAlso known as "real dark mode": https://twitter.com/i/status/1657755808300519426 https://twitter.com/i/status/1657755808300519426
- tonsky 3y agoIt predates it by a few years https://github.com/tonsky/tonsky.github.io/commit/fd5e59a837b0198df16b0e0af24d875af5bba60b https://github.com/tonsky/tonsky.github.io/commit/fd5e59a837...
- vrglvrglvrgl 3y ago[dead]
- pdamoc 3y agoElm started as a FRP language and ended up giving up FRP[1] for ease of use. If ease of use is targeted, signals might not be the best approach. I distinctly remember things becoming easier when they went away. [1] https://elm-lang.org/news/farewell-to-frp https://elm-lang.org/news/farewell-to-frp
- eternalban 3y agoThanks for the link.
- carapace 3y agoThat was one of the most interesting developments in the already fascinating saga of Elm. Czaplicki gets a lot of flak, but it's almost all about how he keeps tight control of his language & ecosystem. No one duns him for brains, because he's clearly very smart. So when he says, "everything related to signals has been replaced with something simpler and nicer", it's news, eh?
- KRAKRISMOTT 3y agoThe observable syntax is confusing. Lisp uses *earmuffs* syntax for global variables, if you only use one muff for an observable variable, how would you express a global variable that's observable? Using **lopsided muffs*?
- Bogdanp 3y agoI took a similar approach in my Racket library, gui-easy[1,2]. Though I opted to not defer any computations. Any observable (similar to a signal from the post) update propagates to observers immediately, and there's no incrementality -- observables are just boxes whose changes you can subscribe to. Regarding the disposal problem, I used weak references and regarding the where to take observables and where to take concrete values as input question, I decided that any place an observable can go in, a concrete value can as well and it's been a convenient choice so far. For fun, here's an example[3] that builds the todo UI from the post. [1]: https://docs.racket-lang.org/gui-easy/index.html https://docs.racket-lang.org/gui-easy/index.html [2]: https://www.youtube.com/watch?v=7uGJJmjcxzY https://www.youtube.com/watch?v=7uGJJmjcxzY [3]: https://github.com/Bogdanp/racket-gui-easy/blob/364e8becaafaab739c740a877cbdefed363a32b9/examples/todo.rkt https://github.com/Bogdanp/racket-gui-easy/blob/364e8becaafa...
- itronitron 3y agoThis article would be more compelling if the demo videos showed a UI that actually worked. I'm left wondering if their day job is building consent dialogs for websites.
- tonsky 3y agoWhat do you mean by “actually worked”?
- itronitron 3y agoThe checkmark never disappears in the checkboxes however the standard behavior for a checkbox is that the checkmark should disappear (or appear) when the checkbox is toggled. Furthermore, towards the end of one of the videos, the checkmark seems to turn from green to gray and back to green again with a single click.
- EMM_386 3y agoTopical, because Angular 16 was just released with ... Signals. Used in some places to simplify RxJs or not need it at all. Some, not all. They are looking towards a future where they can get rid of Zone.js and its strategry to change detection. I see this as a step along that part. https://blog.angular.io/angular-v16-is-here-4d7a28ec680d https://blog.angular.io/angular-v16-is-here-4d7a28ec680d
- tijssss 3y agoI'm working on a project to make bootstrap work without js. Similar state management, check at https://noojs.pages.dev/components/accordion.html https://noojs.pages.dev/components/accordion.html https://noojs.pages.dev/components/modal.html https://noojs.pages.dev/components/modal.html
- dm3 3y agoInteresting that this is Clojure and it doesn't mention Hoplon/Javelin[0] as prior work. I've used Hoplon/UI[1] to implement a moderately complex web app ~6 years ago. The library is practically a prototype, pretty much dead by now. However, I found the ideas interesting. I find the biggest benefit of using a fringe library like this is the ability to read and understand the whole implementation. It's really simple compared to something like React. [0]: https://github.com/hoplon https://github.com/hoplon [1]: https://github.com/hoplon/ui https://github.com/hoplon/ui
- kennytilton 3y agoFYI, they recently resumed activity on Hoplon. Good to see!
- tonsky 3y agoWell, it’s mentioned in a sense that “Reactive UIs never became mainstream” :( Would be interesting to understand why
- naasking 3y agoNot sure I like the mixed push/pull approach. If you're already traversing the tree to mark nodes as possibly dirty, you might as well recompute the node's value and store it while you're there. Otherwise on pull/lazy update, you're traversing the tree all over again! Terrible for cache locality, particularly for large graphs. You might be tempted to say that the lazy approach might avoid some recomputations, but if a node isn't actually going to be accessed then that node is effectively no longer live and should be disposed of/garbage collected, and so it will no longer be in the update path anyway! The mixed push/pull approach has only once nice property: it avoids "glitches" when updating values that have complex dependencies. The pull-based evaluation implicitly encodes the correct dependency path, but a naive push-based approach can update some nodes multiple times in non-dependency order. Thus a node can take on multiple incorrect values while a reaction is ongoing, only eventually settling on the correct value once the reaction is complete. In other push-based reactive approaches, you have to explicitly schedule the updates in dependency order to avoid such glitches, so perhaps this push/pull approach was picked to keep things simple.
- Gehinnn 3y agoI implemented the eager recomputation model for the observable utilities in vscode [1] and it quickly fell on my feet because of these glitches. In particular this is problematic if you have observable optional state that has inner observable/derived state and someone reactively reads the outer state and then it's inner if the outer one is defined. Then you clear and dispose the outer state and at the same time set some other observable value that the inner derived depends on. With eager recomputation, it can now happen that the inner derived is recomputed, even though the inner state is disposed. [1] https://github.com/microsoft/vscode/blob/fe9154e791eafb4f18d59caa33f674a0ae992358/src/vs/base/common/observableImpl/base.ts https://github.com/microsoft/vscode/blob/fe9154e791eafb4f18d...
- naasking 3y agoYes, if you want to better tolerate glitches you have to separate internal and external reactivity, and only run the external ones after the full reaction is complete. I think this would prevent the scenario you describe, assuming your operators are well-defined. The other option is to use FrTime's approach and only update nodes in dependency order.
- SergeAx 3y agoDesigner of this theme shoud be stabbed with a keyboard.
- SergeAx 3y agoDownvotes are well deserved, I didn't mention that my comment was about night part of the theme.
- slevcom 3y agoThe author does a lovely job of covering a number of the interesting ideas in this space. But reactive programming is such a tough sell. I know from experience. I maintain a reactive, state management library that overlaps many of the same ideas discussed in this blog post. https://github.com/yahoo/bgjs https://github.com/yahoo/bgjs There are two things I know to be true: 1. Our library does an amazing job of addressing the difficulties that come with complex, interdependent state in interactive software. We use it extensively and daily. I'm absolutely convinced it would be useful for many people. 2. We have completely failed to convince others to even try it, despite a decent amount of effort. Giving someone a quick "here's your problem and this is how it solves it" for reactive programming still eludes me. The challenge in selling this style of programming is that it addresses complexity. How do you quickly show someone that? Give them a simple example and they will reasonably wonder why not just do it the easy way they already understand. Give them a complex example and you've lost them. I've read plenty of reactive blog posts and reactive library documentation sets and they all struggle with communicating the benefits.
- ordx 3y agoIs it because of cognitive load? I briefly looked at your project, and I'm still not convinced that your solution is easier to comprehend than function onLoginClick() { validateFields(); networkLogin(); updateUI(); }
- slevcom 3y agoI think you are absolutely correct. In this simple example, it's not easier to comprehend. Especially for someone who already understands how composing software out of function calls works. My goal with that example was to point out how there are implicit dependencies between validateFields() and networkLogin() and updateUI(). They need to be called in the correct order to make the program work. Our library makes those dependencies explicit and calls things for you. It's not a big deal when we have a 3 function program. But when we have dozens or hundreds of interdependent instances of state, those implicit dependencies become a real struggle to navigate. Now we're convinced our library works well. We use it every day. But it's also very reasonable for you to be skeptical. As you say, there's cognitive load. As a potential user, you would need to spend your time to read the docs, try a few out ideas, and understand the tradeoffs. That's a lot of work. I'm glad you took a look at the project, though. The fact that we've failed to make a case for it is certainly on us. Which gets back to my original point. I don't know how to sell a solution to complexity.