First is that if you look at the system as a graph of updates, automatically closing all cycles between variable update and widget means that any additional two-way binding links you add become a cycle, which makes it difficult to implement, model, and debug. If the framework only initially links the variable to the widget or the widget to the variable, the graph is a lot less populated to start with and creating a cycle is much harder.
Second is that the developer ends up wanting more and more complicated transforms over time, and having to implement both directions of them at once is much more difficult than having to implement only one direction, because not only are you writing two transforms instead of one, you also really ought to make sure the transforms are able to be roundtripped without data loss, and also that any invalid states on either side of the transform are handled sanely in some manner. Very few developers think this way; it's one of those places in programming where you really need to approach it with a mathematical state of mind, but it's generally being written by the "I don't see how programming is connected to math" types. (Which is something a two-way binding advocate needs to keep in mind when writing their library; you're not going to get your users to deeply understand the way the library works before they can benefit from it, they're going to want to just dive in and get something useful going.)
These are not necessarily insurmountable problems, but they are fundamental problems to having two-way binding. I think these two things are why it has never really taken off despite the fact I've seen at least half-a-dozen attempts over the years. I can imagine programming language tools that could help with both problems, but as what I'm seeing getting sketched up in my head requires a type system at least as strong as Haskell's to be practically usable without so many holes as to be insignificantly different from what we already have, it's not going to take off anytime soon.
I also implemented my own two-way/omnidirectional data binding system ages ago (before I knew what it would even be called) in Java Swing for another audio UI, and all the challenges you mention are real, but as you say, not insurmountable. Multiple copies of my UI can be controlling the same hardware, and they will all remain in sync without infinite feedback loops, but getting there took lots of work.
Dataflow constraints with solvers like DeltaBlue solve these problems and allow you to incrementally add additional constraints.
I show how this works for a simple example in my paper "Constraint as Polymorphic Connectors"[1]. I also show that constraints are useful for expressing the high-level architecture of many interactive systems and suggest how to two might be connected.
[1] https://www.hpi.uni-potsdam.de/hirschfeld/publications/media...
You may have bodged together the relevant concepts by experience. That works perfectly fine. But there is a faster way to learn and teach it... if you can get people past the idea that there's some sort of virtue in bragging (for lack of a better word) about how math has nothing to do with programming and how little they know about the mathematics involved in programming. It's all way easier if you start with the concept of an isomorphism and learn how to compose them from the beginning, rather than having to rewrite the same concepts in your code over and over again without realizing it.
If not "used correctly" means a buggy app, and it's easy for an inexperienced developer to make those mistakes, you're still in for a world of pain. If you need to be an expert to avoid accidentally screwing everything up, there's still work to be done.
This is the whole "pit of success" thing Facebook talks about, the path of least resistance should be towards a functional nearly bug-free app. Certainly an app where bugs are relatively well-quarantined.
In my own experience, I worked on an app that migrated from a 2WB (forgot the name, sorry) to React, and it was night-and-day difference.
Similar things have been said about memory allocation in 'do whatever you want' languages like C++.
I have the feeling most programmers are just lazy, that's why they use 2WB.
And I can't blame them. Redux is much more boilerplate than MobX, for example.
For me at least, the reason boilerplate bothers me so much is that it impacts the readability of my code. Ideally, I'd like my codebase to express the business requirements of my solution as succinctly as possible - every line of boilerplate code I need to include to express yet again how to make an AJAX call, or update a piece of state, is a distraction from what I'm actually trying to accomplish with the application.
But if you hide boilerplate you not really improve readability. Not seeing what is really happening just creates the illusion of readability.
And if the framework has abstracted away something that you end up needing to understand - well bad framework or bad usage I guess, but still difficult to figure out what's going on.
React's virtual DOM diffing makes components more readable than the previous style of writing code to explicitly manipulate the DOM and totally hides what's really happening. However, we trust that it's going to do the right thing, just as we trust that the JavaScript engine will do the right thing when it JITs and interprets our code.
It's not that people said "Everything is bad, do assembly". It's just that people identified 2WB as problematic and you just shouldn't do that one thing.
The argument I'd make is that the illusion is the point when you're writing well abstracted code. The abstraction makes it possible to write code with the assumption that some specific detail is taken care of automatically. While it's true that the detail is hidden, that lets the author and reader of the code focus on other details that might be more important.
Of course, this doesn't absolve anybody from needing to be at least somewhat aware of the abstractions, but hopefully most of the abstractions can fade into the background most of the time. (Anybody who's ever worked with a buggy compiler can attest to how frustrating it can be when this is not the case.)
I also think it's ignorant to say it's fashionable to avoid two way binding, I've seen teams greatly benefit from avoiding it, teams with less experienced devs and devs who I've previously seen write terrible code. Maybe now two way binding would be easier for them, though I don't know; I'm unlikely to suggest they 180 on tech that has made them much more successful.
Don't write things like that, it always reads like "Noone cares about your opinion, so stop using X and also stop writing about how bad X is"
You could have written your comment without that sentence and it would still convey your opinion :)
> It's currently fashionable to avoid two-way binding on the grounds that it creates all sorts of hard-to-debug problems and slows your application down, and that a one-way top-down data flow is 'easier to reason about'. This is in fact high grade nonsense. It's true that two-way binding done badly has all sorts of issues, and that very large apps benefit from the discipline of a not permitting deeply nested components to muck about with state that might affect distant parts of the app. But when used correctly, two-way binding simplifies things greatly.
I wonder what the author considers "used correctly" and "done badly" and how Svelte approaches this.