Settings

Theme

Incremental – A library for incremental computations

github.com

296 points by handfuloflight 12 hours ago · 64 comments

Reader

jitl 10 hours ago

This style of reactive programming is quite popular in JavaScript UI frameworks these days under the moniker “signals”, with a proposal for standardization here: https://github.com/tc39/proposal-signals#-javascript-signals...

It’s used by frameworks Vue, SolidJS, Svelte, Ember, Angular, and there’s a few different implementations for React like Mobx and Jotai. There’s a few different algorithms for how to propagate changes and evaluate the DAG, I believe SolidJS2 uses a height-based algorithm similar to Incremental.

I’ve been fooling around with an implementation that uses an Int32Array arena to allocate nodes and link them together with linked lists without paying O(dependency edges) GC load: https://github.com/justjake/dalien-signals/tree/dalien-signa...

There are a few of these for Rust as well, Leptos is an example in UI frameworks, and Salsa is an example in general incremental computing, used in rust-analyzer.

Another way to look at this sort of thing is as a build system with automatically tracked dependencies. One such build system is tup, which instruments build jobs to detect what files they read to establish dependency relationships. Interesting reading from the author: https://gittup.org/tup/build_system_rules_and_algorithms.pdf, see also the classic Build Systems à la Carte https://www.microsoft.com/en-us/research/wp-content/uploads/...

  • seanmcdirmid 5 hours ago

    You can build some lightweight dependency graphs that flush out quickly. You don’t need to describe how the signal has changed, just that it might have changed, then flush dependencies on change (some listeners might be notified of a change they don’t care about anymore) and re-register them when they come back for fresh data again.

    But incremental computation isn’t exactly functional reactive programming. They are different domains in practice that often get thrown together because the problem they address can overlap. Incremental computation explicitly derives a function that can operate on deltas, FRP might just use damage and repair instead.

    • jitl 3 hours ago

      my understanding of FRP is the inverse of your description, I use the same definition of “functional reactive programming” as in this Jane Street blog post on the subject https://blog.janestreet.com/breaking-down-frp/ : FRP is history sensitive and wants to compute over some stream of events explicitly - and the sort of events we expect the system to handle are UI events like mouse motions, clicks, and FRP is expected to turn those into application semantics. Self-adjusting computation (like Incremental or js “signals” like alien-signals) is mostly history-insensitive by default because it’s lazy and seeks to recompute a node once during stabilize() no matter how many times an input changed since the last stabilize(). I definitely do not think “signals” in the JS/TC39 formulation of the concept is FRP: not nearly enough attention paid to first-class events.

      but yeah to zoom out, the semantic boundaries and even definitions of terms in this area are kind of fuzzy and i don’t think it’s interesting to try too hard to sort stuff into a taxonomy unless you’re going to define all the parts of your taxonomy and provide a bunch of examples; these terms are not well-defined enough otherwise and arguing about ill-defined semantics is not my idea of a good time.

      For example how to taxonomize something like Signia, which has fair bit of history sensitivity but is mostly concerned with diffs (https://signia.tldraw.dev/docs/incremental#using-diffs-in-co...), like Incremental uses logical clocks for validation, but is much more “pull” oriented in its recomputation/“stabilize” algorithm? To me it sounds like you’d put it in the “incremental computation” bucket because deltas; but Incremental itself doesn’t seem too concerned with deltas at the root of the library, i didn’t find any mention of them in the into docs and think it sounds more like js “signals”. https://github.com/janestreet/incremental/blob/master/src/in...

      • seanmcdirmid 3 hours ago

        You might want to go over the original Elliot/Hudak work? It’s a bit obtuse since it’s all in Haskell, but signals were never meant to be historical accumulators unless you set them up that way. Signals are continuous values, they don’t reveal discrete events like streams do, that’s the main distinction between them, your signal computations then can be viewed as a continuous value derived from other continuous values.

        In early FRP systems, you could just plug in a t and compute a signal network (well, behavior) at any point in time, so the entire history of values needed to be saved. Modern FRP abondons first class time and focuses more on change propagation (you are told when your signal computation changes at some end point, otherwise no history is saved).

        Anyways, it’s been a decade since I’ve done this stuff. Erik Miejer kind of muddied the waters with his observable stream work, and people started calling that FRP instead. I see the Jane street blog post is based on Evan’s work, I don’t remember much about it.

        I’ve always implemented purely non-history preserving signals (Modern FRP) just because they are incredibly easy/efficient to implement and give you 99.9% of what you want and…first class time is just not useful unless you are doing simulations or something (I’ve never called my work FRO, just FRP inspired). It’s also easier to explain to people a continuous time function (the UI label changes as the values it is bound to change). I even have some work where I translate it down to WPF data binding in C# using the DLR to compile signal expressions.

        The incremental computation landscape is pretty vast, but I always found that memoizing sub-trees of a computation and then doing a standard damage and repair algorithm to recompute subtrees that depend on changed values works well enough for most use cases. Again, not really related to FRP, but I somehow got involved in both worlds a couple of decades ago. You could try to define a function over the delta, but it’s incredibly dependent on the domain unless you restrict yourself to a differentiable programming language.

        • lioeters 27 minutes ago

          I assume you mean "Functional Reactive Animation" (1997) by Conal Elliott and Paul Hudak? While searching for this, I found a great collection of papers on this topic of Functional Reactive Programming in the Haskell language wiki. Most are by Elliot and/or Hudak. Good stuff!

          https://wiki.haskell.org/Research_papers/Functional_reactive...

          This feels related to many other topics and various strategies, like incremental parsing/compilation/computation, reactivity, CRDTs (conflict-free replicated data type) and distributed computing. About coordinating state changes, reconciling differences, managing dependencies and derived values.

          @jitl, the "Data oriented signal library", dalien-signals, looks very interesting and useful, particularly for efficient UI rendering like large tables or maybe editors. I'll enjoy exploring it.

  • avallach 8 hours ago

    Another notable example: JetBrains Noria ( https://blog.jetbrains.com/fleet/2023/02/fleet-below-deck-pa... ).

    As the authors highlight: "Noria is not a UI framework at its core. Instead, it’s a platform for incremental computations.". But currently they happen to use it to optimize gui rendering in JetBrains Air IDE.

  • tgv 5 hours ago

    It looks as if there is a significant difference in treating updates to complex objects, and probably scheduling as well.

ronfriedhaber 8 hours ago

This is cool.

As far as I can tell, incremental the library aims to solve the problem of partially hydrating a computation graph when source data is altered. This approach is similar to the one pursued by (well designed) build systems and is common in the FP world. [2] This has many use cases and is very cool.

In addition, in the sphere of incremental computation, there exists Differential Dataflow, Timely Dataflow (adjacent), and DBSP. Systems like Feldera are built on DBSP. Materialize is lead by some DD guys.

Personally, I am pursuing an orthogonal approach specifically for the problem of financial data and financial workloads, There exists huge, very important problems to solve! [1]

[1] https://modolap.com

[2] Signals And Threads episode on the subject https://signalsandthreads.com/build-systems/

  • hantusk 44 minutes ago

    Check out https://github.com/ila/openivm which implements a very large scope of aggregations as incremental operations in an SQL-to-SQL compiler, and extension for duckdb that automatically maintains a materialized view

  • valzam 7 hours ago

    > https://modolap.com

    Redirect to a 2k USD stripe payment with no explanation when clicking on the main callout button is a pretty baller move.

    • ronfriedhaber 6 hours ago

      One man's baller is another man's insufficiently baller.

      Email me for details, pricing & installations, or your target use case, would love to talk. In addition, if you have any feedback.

      ron at modolap dot com

      • foltik 3 hours ago

        Your site has a bunch of marketing copy, trademark symbols, and a link to pay you $2k/mo, but zero technical detail and is broken on mobile. It gives vibe coded.

        The benchmark of 2m black-scholes evals/sec is meaningless without additional context. Also seems a couple orders of magnitude slower than what I’d expect even for a single core. What exact transformations are being done, and what’s the throughput in GB/sec? Is it multi threaded? Benchmark against the equivalent query in kdb+ and ClickHouse?

        • ronfriedhaber 2 hours ago

          > is broken on mobile

          Not true as of last deployment.

          > but zero technical detail

          What additional technical detail would you be interested in?

          > Also seems a couple orders of magnitude slower than what I’d expect even for a single core.

          1) it's more than 2m/s on a MacBook AIR 2) Most of the latency is networking. This was a preliminary setup with a client / server over websockets where the book of 1m contracts combination of (strike, exp_date). Each insert only after a corresponding query has been returned (over the wire) from the previous insert. I can agree the eval isn't great; as in, the throughput is much higher.

          > What exact transformations are being done

          This is provided in the blog post.

fadesibert 11 hours ago

Goldman took the same approach with instrument pricing ~30 years ago. I recall long discussions about "Node Purpling" in my ~13 year tenure there.

Computer Science has evolved, and AFAICT this is not a graph approach, but things like differentiation are computationally expensive, and therefore you want to minimize the number of times you do it to as close to the theoretical minimum.

Edit: Related HN discussion https://news.ycombinator.com/item?id=36006737

  • zitterbewegung 8 hours ago

    Yea and this created "bank python" informally a good article is here.

    https://calpaterson.com/bank-python.html

    The best description about how it became a problem is one of the paragraphs.

    "New starters take an exceptionally long time to get up to speed - and that's if they don't resign in fit of pique as soon as they see the special, mandatory, in-house IDE (as I nearly did). Even months in, new starters are still learning quite fundamental new things: there is a lot that is different."

    I think it took me til I was there around two and a half years to fully comprehend it when I was working on it. Not much modern training til they figured out they had to teach it again that was better. The worst part is to make an UI around it coding it and it wasn't approved for new projects.

    • rwmj 5 hours ago

      That article was fascinating, thanks for sharing it. I wonder if all banks use the same "bank Python" or if they all forked it in different ways?

djtango 10 hours ago

I was very curious about Dataflow programming years ago - I think a lot of people were coming at this problem from various angles. This specific library immediately reminded me of Javelin from Clojure [0]

[0]: https://github.com/hoplon/javelin

osener 5 hours ago

If you find this interesting, also check out their UI library called Bonsai that built on top of Incremental: https://github.com/janestreet/bonsai/

Libraries like React are pretty efficient with skipping work by using Virtual Dom, but constructing this vdom still takes time. Bonsai makes the vdom incremental and it is pretty fun to work with.

I built a desktop UI library with it by targeting (now unmaintained) Revery. It is using a much older version of Bonsai however: https://github.com/ozanvos/bonsai_revery

RandomBK 11 hours ago

One thing I've never fully grokked is how this differs from an observable pattern where one can publish new values to inputs, propagate that through the computation, and push newly computed values to listeners.

I guess there's probably optimizations around change detection and stopping the propagation if there's no change (though observables can do that as well). The stabilize command also makes things interesting as a way to batch changes together before recomputing (but again, doable with observables too).

Is the delta primarily coming from introspection and automatically building the compute graph? Or is there something more fundamental that I'm missing?

  • Eliah_Lakhin 10 hours ago

    It depends on how you define the observable pattern.

    The fundamental components here are laziness and weak connections between graph nodes. Node values are getting materialized only when you observe them, and the system is flexible for live structural changes.

    Usually, you don't need to materialize the entire graph when you need to observe just some nodes. Additionally, you can halt computations at any point in time leaving the graph in semi-actualized state, make extra changes to the inputs, and continue materialization of the nodes of interest. The algorithm will sort out all changes for you.

    Essentially, incremental computations is just a term covering these features. You can organize the same system in terms of observers and subscribers.

    Perhaps, classical Excel spreadsheets is the best illustration of the idea. Also, see my article on the topic: https://medium.com/@eliah.lakhin/salsa-algorithm-explained-c...

    • RandomBK 10 hours ago

      Laziness and weak connections makes sense as differentiators.

      However I'm not sure Excel is such a great illustration in that case, as it's neither lazy nor weakly connected; at least at the surface.

      • vanderZwan 7 hours ago

        So I remember having physics homework a quarter century ago at university where we had to use Excel to determine a static electric potential field, by defining all cells in the grid as "the average value of the four surrounding cells", except the edge cells and source/sink cells which would get actual concrete values.

        I distinctly remember being amazed that it would just iterate until it reached equilibrium (maybe we had to change some setting somewhere first though, I never really used Excel before or after that). I think you could change a value mid-iteration, and the grid would just continue on with the previously calculated cell values, instead of start over from scratch. That's basically "weakly connected", isn't it?

        (anyway, the real challenge is to find a practical use for Excel's hidden fractal powers https://www.youtube.com/watch?v=b-Fa6HtvGtQ)

    • geysersam 8 hours ago

      What does weak connections mean in this context?

      • Eliah_Lakhin 7 hours ago

        I meant that the graph (DAG) structure is not necessary need to be sealed and defined upfront. It could be computed and changed on the fly by the same function that computes node's value. Assuming that the node value computation function is a pure function without side effects (e.g. it's output depends purely on inputs) the function may read other node values directly, and the act of reading would establish graph edges transparently for the user. The next time compute function is being invoked it could re-subscribe on the different nodes hence changing graph structure on the fly. The user also can remove or add new nodes in between of the node values materialization. In other words, the act of subscription between nodes in the incremental computation system is typically tracked more transparently for the user than in the system with explicit observer-subscriber primitives. Even though, this is implementation dependent. The observable pattern could be designed transparently too. Perhaps, "flexibility" would be better term.

        • jimaway123 3 hours ago

          How do you consistently update a DAG, like you describe in your medium article, if the functions corresponding to the nodes in the graph are free to create new dependencies willy-nilly? It seems like this would have to be pretty restricted and/or performance killing, because you'd have to evaluate the graph under the assumption that any node could depend on any other node (unless that dependency creates a cycle, presumably). This overhead may not matter if the node functions are expensive relative to the cost of managing the graph, though.

  • iamwil 6 hours ago
  • blovescoffee 11 hours ago

    Roughly, you subscribe and listen to an observable. Incrementals are more like a cache across some DAG of computation + state that lets you optimize by only recomputing what needs to be recomputed.

    There's a really good talk from Ron Minsky here: https://www.janestreet.com/tech-talks/seven-implementations-...

  • runtime_lens 10 hours ago

    That's how I was thinking about it too. At first glance it feels very close to reactive programming with dependency tracking. I'm curious whether the real advantage is the API ergonomics or if there are optimizations under the hood that wouldn't be practical with a typical observable implementation.

  • AlotOfReading 10 hours ago

    I mean, it's fundamentally just a graph, but it's a way of correctly and efficiently computing changes in massive, dynamic graphs. Let's imagine you have a diamond shaped subgraph that fans out to hundreds of intermediary nodes before collapsing down again via and paths with different "lengths". And what if some of those paths have e.g. min(A, B) where the max side is the only one changing?

    A naive observer approach will 1) compute that potentially exponential blow-up very inefficiently and 2) probably have "concurrency" issues. This library will be close to optimal and correct, even if you start dynamically changing the graph structure.

    But yes, you can achieve the same thing with observers and other kinds of approaches. Most of them just a lot harder to get right while avoiding performance cliffs.

runtime_lens 11 hours ago

One thing i have always linked about Jane street projects is that they tend to package ideas that have existed in research or niche system into something developers can actually use. Even if you never adopt the library, the design docs are usually worth reading.

eyupucmaz 2 hours ago

Interesting to see JaneStreet's take on incremental computation. As a frontend engineer working with React state management (Zustand, Jotai), the concept of granular re-computation is very familiar — though the OCaml type system gives this a level of correctness guarantees that JS solutions can only dream of.

pgt 6 hours ago

Electric Clojure does incremental rendering that crosses the client/server boundary: http://electric.hyperfiddle.net/

The closest thing to Electric (IMO) is SolidJS, but frontend only: https://www.solidjs.com/

shortercode 4 hours ago

Oh cool I was working on something similar awhile back and couldn’t find much precedent. The project got retired as our use case went away. But I’d love to revisit it. It’s still on npm as “data-rambler”.

Idea was a DSL that could be loaded into a JS runtime, then fed data streams. Your module would transform that data into various output streams which could then be fed to a separate reporting library. Powering dynamic reports based on a template.

Our first iteration was already pretty powerful but I had some big plans to bring it into a more JS feeling syntax to reduce complexity.

jimaway123 3 hours ago

Is anyone aware of a version of this focused on very speed sensitive, low-level incremental computation? Like perhaps a compiler that generated a kernel for doing an incremental computation on a static graph?

  • tempfile 3 hours ago

    Given this is a Jane Street library, I would be surprised if it were not very speed-sensitive already.

  • erichocean 3 hours ago

    https://github.com/cmuparlay/psac is extremely fast, I use it to recompute sculpting topology updates (think: Zbrush DynaMesh) at 60 fps.

    • jimaway123 2 hours ago

      Yes I can see it would be pretty fast, but the computation is defined at run time and its evaluation uses pointers to the various nodes of the computation graph. This limits what optimizations be applied (ie inlining, vectorization, reordering, etc). I'm just curious if anyone has taken this a step further and written something like PSAC except with a code-generation step that creates an optimized implementation of psac_propagate() for the specific computation graph.

evomassiny 6 hours ago

Can't you solve it using hash trees (or Merkle trees) ?

You tag each computation nodes with a hash of its dependencies and some constant salt, that gives you an ID which identifies the results that the computation node would produce; before running it.

You can then use those IDs to index the computations results in a cache; whenever you query a computation results, as long as you update the IDs of each leaf of the computation graph, you will only re-compute the nodes that need to be updated

iamwil 6 hours ago

For those interested in incremental systems, I recommend checking out DBSP. I thought it was pretty neat.

lsuresh 4 hours ago

Over at Feldera, we focus on IVM for SQL, but incremental computing problems show up far and wide: UIs, spreadsheets, control planes, compilers and more.

srean 6 hours ago

It might be worth reading this in concert with

https://hn.algolia.com/?q=flow+based+programming+

raphinou 10 hours ago

I think websharper's Var are similar to this, and it is really great to develop dynamic web interfaces (in fsharp).

geoHeil 9 hours ago

I maintain a similar library more focused on data engineering needs: https://docs.metaxy.io/ maybe it is useful for more people.

dh2022 10 hours ago

In C# a dependency graph that automatically updates only the affected dependencies can be implemented using events and/or functors and/or data binding.

I do not understand what is the big deal with Increment. Is it more efficient because it is written in OCaml rather than C#?

geokon 7 hours ago

Where is it actually explained how it works..?

Usually these kinds of systems either don't scale dynamically or have caching issues. The first example, a spreadsheet, is "easy" because there are a fixed amount of cells to track. A GUI can be a lot harder (imagine sub windows and sub-sub windows dynamically popping up and tracking some redundant and some unique "computations". Entities can appear and then be removed at random). Though the wording carefully says "constructing views" so maybe it doesn't handle dynamism

Balinares 7 hours ago

Whenever I see something in OCaml I assume it's Jane Street and that ends up correct a surprising amount of the time.

  • shikck200 7 hours ago

    Jane street was/is a top user of ocaml, they kind of flushed their reputation down the toilet with they massive fraud in the asian markets. IIRC the entire company got banned from trading inside India, hongkong etc.

    After they got caught most of their online posts/videos either got deleted, or locked so you could not comment on them.

    I had hoped their front guy for ocaml, Ron Minsky had atleast made some sort of statement. Currently no ones know how deep that fraud went, but what we know ocaml was used in the fraud, damagine ocaml ecosystem too.

erichocean 4 hours ago

Parallel Self-Adjusting Computation[0] is a fun entry in this genre.

[0] https://github.com/cmuparlay/psac

xiaodai 5 hours ago

pardon my ignorance but is Ocaml performant enough? Why isn't something like this coded in say, C++?

arthurbrown 7 hours ago

Another related but semantically distinct package with great developer ergonomics is the FRP library React -- https://erratique.ch/software/react/doc/React/index.html

Very satisfying to use when you manage to find a problem that is suited to this type of approach.

mempko 11 hours ago

I have built something similar like this for my fund 7 years ago. We were doing parametric optimization on large computational graphs. I have never programmed Ocaml but my understanding is introspection is kind of a weak spot for the language. Curious language choice! I know Ocaml is fast, about 1-2x speed of C, on par with Java.

curtisblaine 10 hours ago

This reminds me of https://github.com/electric-sql/d2ts. Not sure they're comparable, but they seem to be aiming at the same problem.

reinitctxoffset 11 hours ago

Kobe!

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection