• This triggered some associations for me.

    Strongest was Cells[0], a library for Common Lisp CLOS. The earliest reference I can find is 2002[1], making it over 20 years old.

    Second is incremental view maintenance systems like Feldera[2] or Materialize[3]. These use sophisticated theories (z-sets and differential dataflow) to apply efficient updates over sets of data, which generalizes the case of single variables.

    The third thing I'm reminded of is Modelica[4], a language where variables are connected by relations (in the mathematical sense). So while A = B + C can update A on when B or C change, you also can update just A and B, then find out what C must have become.

    [0] https://cells.common-lisp.dev

    [1] https://web.archive.org/web/20021216222614/http://www.tilton...

    [2] https://www.feldera.com

    [3] https://materialize.com

    [4] https://modelica.org

    • > Strongest was Cells[0], a library for Common Lisp CLOS. The earliest reference I can find is 2002[1], making it over 20 years old.

      How about Microsoft DirectAnimation[1] from 1998, literally designed under the direction of Conal Elliott? Serious question, for what it’s worth, I’ve always wondered if all discussions of this thing are lost to time or if nobody cared for it to begin with.

      [1] http://sistemas.afgcoahuila.gob.mx/software/Visual%20Basic%2...

      • ... or Visicalc, TK/Solver, etc.

        I've always been baffled that people think spreadsheets are like dataframes when the really interesting thing has always been you can write formulas that refer to each other and the engine figures out the updating. Most of the times I've written a spreadsheet I haven't used the grid as a grid but just a place I can write some labels, some input fields and formulas.

        • well it is both an easy way to compute in a dataframe context and a reactive programming paradigm. When combined, it gives a powerful paradigm for throwing data-driven UI, albeit non scalable (in terms of maintenance, etc.).
  • What the author touches on with before and after "declarative thinking" is largely applicable to all Directed Acyclic Graph (DAG) workflows, and not just signals. They are 100% correct that there is a mental shift. Yes, you can use magic to implicitly declare your DAGs with signals. You can also be really explicit with dependencies.

    DAG-based workflows incur a cost in terms of complexity, but there are a few advantages to using DAGs instead of sequential.

    1. Parallelism becomes inherently built-in

    2. It's easier for a new developer to understand the direct dependencies of a node on other nodes (compared to sequential). Sometime in the future, a developer may want to split off a task or move it up/downstream of other tasks.

    3. Fault tolerance & recovery becomes easier. Just because 1 step fails, doesn't mean that the whole workflow must come to a halt.

    • What makes signals DAG?

      User caution or does e.g. this lib prevents cycles?

      • The Signals libraries I know of (including reaktiv) are throwing an exception if a dependency cycle is detected in the computation step. But that doesn‘t solve infinite executation loops completely because the user still can define an Effect that updates its (in)direct dependencies - that means User caution is still needed. Angular tried to prevent that in their older versions by not allowing to set other Signals in Effects. But they reverted that decision.
  • twic

      y = Computed(lambda: calculate_y(x()))
    
    How does this instance of Computed know that it depends on x? Does it parse the bytecode for the lambda? Does it call the lambda and observe which signals get accessed?

    In my homebrew signal framework, which emerged in the middle of a complicated web dashboard, this would look like:

      y = Computed([x], calculate_y)
    
    So the machinery gets to see the signal directly.
    • I am using the standard Python library `contextvars.ContextVar` as the foundation of my reactivity system's dependency tracking mechanism. In the computation step, when Signals get accessed, I track them as dependencies.
    • I've used systems that did this (some TypeScript TUI library comes to mind) and was similarly confused. I think what actually happened was that the x function/getter/whatever had some 'magic' in it that let it communicate with `Computed` as a side-effect of `Computed` computing the value.

      Too magical for me. I'd rather have something like you described where inputs are explicit, so I don't have to guess about whether the magic will work in any given case.

    • The module probably has its own global register and ever time Computed() is called it adds to it.
  • One of the largest, if not the largest python codebase in the world, implements similar ideas to model financial instruments pricing: https://calpaterson.com/bank-python.html.
  • I've been writing front-end javascript the "just use functions" way and never really wanted to get into React because it looks too complicated. But this makes sense. God damn it I want to actually learn react now.
    • This article never mentions React. This has nothing to do with React. There's a reason people say "react is not reactive"[1].

      Signals are derived from Observables[2] which were first used in Adam Haile's S.JS[2] and made popular in JavaScriptLand by Ryan Carniato's SolidJS[3].

      [1] - https://dev.to/this-is-learning/how-react-isn-t-reactive-and...

      [2] - https://dev.to/this-is-learning/the-evolution-of-signals-in-...

      [3] - https://www.solidjs.com/

      [4] - https://github.com/adamhaile/S

      • Fwiw knockoutjs seems to predate s.js (2010 vs 2013)

        I can’t remember if at that point it was the first lib to uses observables.

        • Oof! I can actually still see a link to Knockout.js in my clipboard, so I clearly meant to add it as a source for that post. I also got the numbering all wrong, so I guess I should have spent another minute checking it for accuracy.

          Thanks for the correction so that other people can learn!

      • Since GP mentioned React, I think MobX was more popular there and seems to predate SolidJS. Also, Valtio is a modern version of the same idea but with a lot less boilerplate.
      • > made popular in JavaScriptLand by Ryan Carniato's SolidJS[3]

        Are you sure it hadn't been, by chance, made popular even before by KnockoutJS?

        • Yeah, as I said in response to another commenter, I actually had a link to Knockout in my clipboard. I'm pretty sure I was supposed to add the link after the word Observable.
        • Actual popularity to the point that they are now being adopted into the standard (not to say all frameworks except React) came thanks to Ryan. Though he explicitly acknowledges that original ideas come from Knockout, S and Marko
    • React is very far from signals (and very far from sane state management). Better alternatives:

      - SolidJS (kickstarted the whole signals revolution)

      - Svelte

      - Preact (and Preact Signals)

      - Well, even Angular got signals now

      • I actually created the library after being exposed to Angular Signals starting from the v16 release. I watched some talks and read articles about Signals, just to know about Ryan Carniato from SolidJS. He did an excellent job teaching the world about Signals!
      • React doesn't really make many assumptions regarding state management. You're free to pick the library you want. Redux used to be the standard, but I worked on applications purely using RxJS, the way signals are presented in this article.
        • > You're free to pick the library you want.

          They are all still pretty hampered by React's model: re-render (internally, in VDOM) the whole component on any minor change.

      • There's also Datastar that uses signals and ends up being the best of HTMX and Alpine.js combined (at a smaller bundle size too).

        https://data-star.dev/

    • look into the Java observer pattern
  • Two perplexing aspects:

    - Why so many lambda functions instead of regular named functions? Is it a technical limitation? Something important should have a name, for instance (for the example in the article) different ways to compute greetings from names.

    - How are the computations ordered, particularly multiple Effects that trigger at the same change? For instance, in the example in the article, when the name changes the updated greeting is printed before or after the updated location.

    • You can use normal names function instead of lambdas if you prefer! In Javascript anonymous functions are used normally for things, where defining named functions are considered too verbose - I use lambdas for that in Python.

      The Signals evaluation are topologically ordered and are running synchronously. Effects are running in the order they are defined.

  • I've designed something like this before but in the context of orchestration. A 'read' implicitly became a subscription in the meta-process and any subsequent changes to the consumed value triggered updates to the consumer process that were either explicitly caught or handled through an implicit context process.

    This allowed process definitions to remain simple enough for business analysts to comprehend while still able to cover real world complexity.

  • I have a dream for a compiled reactive DSL for video game programming that makes replay and rollback netcode automagically, eliminates bugs in state management, and naturally expresses derived state and the simulation step/transition function, while still being performant enough for real time

    The performance hit from all that indirection of registering, getters, setters, discover, traversal, and lambdas could be avoided if we could compile the dag into smartly nested ifs

  • Hmm I have mixed feelings about this. I've thought about this topic for a bit, a couple of years ago I thought of bringing functional reactive programming to a backend node.js project (partially because of managing callback hell); in the past couple of years I work on an event/workflow system with 100+ million events per day.

    This feels like a lighter weight alternative to Temporal or other workflow tools[0], but eventually for a backend system you'd likely be rebuilding features that are tailored for the backend.

    In frontend code, you have many side effects (e.g. DOM, styling) that rely on a single piece of data/event, and more side effects that rely on those side effects (e.g. component hierarchy), and having this laid out declaratively is one way to understand the behavior of an application when this piece of data changes. You are also almost never concerned about durability/persistence of the state of data on the frontend, just because the code interacts with the browser and we almost never question the reliability of that API. A human is typically the "driver" of these interactions and is typically in the loop for most interactions, so stuff that fails, e.g. a failed network call, can bubble up to the user to deal with.

    Conversely, web backend projects have code and infrastructure that are distributed (even monolithic ones), and most of the time are concerned with persisting state/data, distributing/scheduling workloads, etc. Each side effect / computation, especially ones that cross networks/service, has its own requirements for whether it should be at least once/at most once, retried/retry patterns, latency/throughput, failure modes/error handling. These requirements also define your boundaries/interfaces, rather than a nice semantic and declarative one (not exclusive but oftentimes the requirements win out).

    Not saying that this signal-based approach can't be used in some areas of the application would benefit for declarative computation, but the examples given seem to indicate also a desire to do distributed workflow stuff.

    [0] https://temporal.io/, https://github.com/meirwah/awesome-workflow-engines

  • For bigger workflows, this declarative pattern is already implemented by orchestrators like Dagster, Flyte, and recently Airflow; e.g., https://dagster.io/blog/declarative-scheduling [fixed]