Interesting that you still stand by that, even after Apple moved to the exact same model with Swift UI.
Narrowmindedly worrying about syntactical differences is contributing nothing to the conversation. The point is relinquishing control of state to the framework (be it via props, hooks or @State), and drawing the UI as a pure representation of whatever the framework tells you. Hence ui = f(state). This gets you a metric ton of advantages, which is why every modern framework is doing it.
Classes, by themselves, are inadequate as containers for state unless that state must only exist in memory and never be observed, synchronized or serialized. You can attempt to patch that with decorators, ORMs or whatever, but now you're doing the same thing as React is doing with functions.
Interesting that you would think Apple introducing Swift UI would have any bearing on the correctness of that blog post.
Why do you think it would?
Did you misread my post as "ha ha, Apple does it differently and therefore react is wrong"?
If so, you certainly misread it a lot, and you have the wrong person. I have tons of posts about Apple getting things wrong.
Anyway, did you miss the following part?
I also think that despite all the flaws, react.js and react.native are currently eating native development's lunch.
Clearly I made the point in my post that this approach has a lot of mindspace, despite the obvious flaws, so not sure how you pointing out that this approach has a lot of mindspace invalidates my post.
> Hence ui = f(state).
Every UI is always some mapping of the state. Otherwise it isn't really a UI, is it? What would it be, in your opinion, if it didn't map the state?
> pure representation
What does "pure representation" mean to you? I covered in the post that it sure as hell isn't a pure function of the state. And cannot be.
Did you miss the part where David Abramov conceded that point?
To elaborate a bit, React components aren’t always “pure” in strict FP sense of the word. They’re also not always functions (although we’re adding a stateful function API as a preferred alternative to classes soon). Support for local state and side effects is absolutely a core feature of React components and not something we avoid for “purity”.
> This [ui=f(state)] gets you a metric ton of advantages,
Well, yes. Like being a working UI, for example, which is why MVC is also structured this way.
But can you elaborate the specific advantages you believe the react approach gives us over, say, MVC?
The post elaborates that it would be really nice if UI were a pure function of the state. But it just isn't. And trying to pretend that it is might seemingly buy you some of those advantages, but at a huge cost.
Maybe it's a me-thing, but when I see the creators of a system I trust basically deprecate it, then I try to re-evaluate my viewpoint. Also, when I read the post a few years back, I honestly thought that it must have been a product of its time.
> Every UI is always some mapping of the state.
Sure, but it previously wasn't described as such. It was described as a list of imperative operations "if x do that" (unless the developer just gave up and had a giant refreshEverything() function).
IIRC, at the time, MVC had a common problem of controller bloat, precisely because it was trying to perform the reconciliation between view and model imperatively rather than simply describing it as a function. MVVM solved this partially with data-binding, but depending on the framework, you still had lackluster handling of derived state inside the view-model.
> What does "pure representation" mean to you?
I mean that, the render function of the component is a 100% pure function of the state that the framework injects. By "render function" I mean everything except the statements beginning with `use...`. Those are just React's syntax for defining the component. Another framework such as SwiftUI might have defined them outside the function.
> But can you elaborate the specific advantages you believe the react approach gives us over, say, MVC?
There are so many resources on this, so I don't know what I can say to convince you, but I'll try:
* The Compiler. Regardless of whether you think it’s self-inflicted complexity (I don’t), functional patterns generally are easier to statically analyze and transform.
* Transitions, concurrent rendering, suspense, & time-slicing. All of these are possible by rendering components with outdated state. A low priority update may trigger a loading state and defer its commit. Meanwhile, high priority updates can continue to work as usual. In the past ~2 years, this is easily where I've seen the most UX & DX gains in frontend.
* Most importantly: readability. Even if components are only 90% pure, I still see side-effects and state clearly marked. Even in MVVM, I've seen so much spaghetti from my coworkers. In React & post-React frameworks, when debugging a component, I have a much narrower range of things I need to check. For example, I don't have to worry that an object I'm reading from is being mutated by a component on a completely different route.
Overall, the reason why this post rubs me the wrong way, is that you took a theoretical design document, misunderstood it for the actual, practical implementation and snarked at the authors for not going with the "obviously correct" solution of OOP. Not seeing the bigger picture in 2018 is understandable, but now it's... interesting.
> IIRC, at the time, MVC had a common problem of controller bloat,
Nope. Non-MVC implementations had the problem of controller bloat, because they didn't actually implement MVC.
> the render function of the component is a 100% pure function of the state that the framework injects.
Except, as I've correctly pointed out: it's not.
[Advantages]
None of this is in any way specific ("The compiler". Seriously?) or clearly an advantage that can be tied to this type of framework. So yes: you're not even close to convincing me. Because there's no "there" there.
> the reason why this post rubs me the wrong way, is that you took a theoretical design document, misunderstood it for the actual, practical implementation
You misunderstood the post. Completely. The document claims "pure function". This is laughably false. The question is what is left when you remove "pure" from "pure function". And the answer to that is: nothing.
That doesn't mean there are no benefits, but the benefits cannot be "what you get from being a 100% pure function", because it ain't.
Just like the benefit of butter can't be "it's 100% fat free". Because it ain't.
And if you keep insisting that those are the benefits, then I don't know how to help you.
And again, it doesn't mean there are no benefits, but they are both smaller than and quite different from what you claim. And with the benefit being fairly small, the other question is what the cost is of pretending this is so when it is not. And the answer is: pretty high.
And it's funny that on the one hand you go with the same "well, you're taking the "100% pure" thing too literally", when just a few lines above, you yourself made "100% pure function" the defining characteristic.
So which is it? Make up your mind. It appears to be Schröding-important.
Any UI that actually is a UI is some sort of function of the state. Otherwise it is not a UI but random graphics and/or decoration.
I don't want that. I don't want to care about screen readers (unfortunately I have to). I want a system where I can pick well-defined rules and then css can style it, screen readers will understand it, automations can parse it, keyboard navigation is free.
Obviously thats not what we got, but I feel like the set of established UI patterns is manageable enough that it could be built.
A great example is the new <select> styling that developers styled in all kinds of creative ways. Now give me that for comboboxes, trees, data-grids etc...
There are already well-defined rules, you just don't like some of the rules, e.g. you can't (today) style <select> options. Keyboard navigation is free as long as you follow the rules about which elements are focusable.
You shouldn't have to care about screen readers the same as you shouldn't have to care which browser someone uses but you always have to care about people; people who can't see or hear what you create, people who can operate a keyboard (or keyboard-equivalent) but not a mouse or touchscreen, people who can use a touchscreen but not a physical keyboard, etc.
It seems obvious that they plan to eventually drop VSCode.
I'd be willing to take them up on that offer. Their agent window is genuinely better as a starting point.
What annoys me is how little they want to integrate with ...anything. Wanna open a link in your default browser? Use our built-in chromium fork, we insist. Wanna open a location in Zed? No, please use our half-baked editor re-implementation. Wanna open a location in Cursors own vscode-based editor? You can't. Managed to work around that somehow? We changed your files to "Worktree TS", disabling all your language servers. It's like programming on an iPhone.
Textbook marketing speak: “Don't you want more relevant ads?”.
It assumes that “ads” = useful information, but that's rare at best. Most ads focus on stealing your attention and creating a fear of missing out. NordVPN isn't educating you. They just manufacture a need and then hope that you won't invest time in researching a better option.
Is it that rare? Sure, there's no advertising profile for "hates VPN ads" but eg an adult male doesn't want ads for women's period pain medication and similarly an adult woman doesn't want ads for male testosterone or other male-coded enhancements ads. Then you get into niche interests like fishing or sewing or 3d printing.
You're conflating correctly targeted ads and useful information.
If you sell gambling ads to an addicted gambler, the gambler doesn't get useful information.
Niche interests might get a pass. But then again: if I’m getting an ad for a 3D printing product on a 3D printing review site, its very likely that the advertised product wasn’t actually the best and is just artificially pushed on me.
I genuinely would rather see ads for products I might like yet do not know exist instead of purely random ads. I don't understand why a person wouldn't.
What I have done to maintain the integrity of my Time Machine backups (to UnRAID, via SMB):
For the "sparsebundles break" issue:
* Back up to multiple targets. I use both mbentley's Time Machine Docker image (only one backup per source machine) and UnRAID's built-in Time Machine functionality (multiple backups of same machine allowed).
* Use spaceinvader1's macinabox Docker image to have a local way to `fsck_apfs` the above sparsebundles.
* When one irreparably breaks, delete it and replace it with a copy of a working one from another of the above targets.
For the "backups are incredibly slow" issue:
* One of the above targets is to an SSD.
* Use TheTimeMachineMechanic's "Speed" option after a backup to determine the slow spots. Look at patterns in "Current:" lines. Pumping the output to an LLM is very helpful here.
The discussion around async await always focuses on asynchronous use-cases, but I see the biggest benefits when writing synchronous code. In JS, not having await in front of a statement means that nothing will interfere with your computation. This simplifies access to shared state without race conditions.
The other advantage is a rough classification in the type system. Not marking a function as async means that the author believes it can be run in a reasonable amount of time and is safe to run eg. on a UI main thread. In that sense, the propagation through the call hierarchy is a feature, not a bug.
I can see that maintaining multiple versions of a function is annoying for library authors, but on the other hand, functions like fs.readSync shouldn’t even exist. Other code could be running on this thread, so it's not acceptable to just freeze it arbitrarily.
Maybe I am missing something. But the function coloring problem is basically the tension that async can dominate call hierarchies and the sync code in between looses it's beneficial properties to a degree. It's at least awkward to design a system that smoothly tries to blend sync that executes fast and async code that actually requires it.
Saying that fs.readSync shouldn't exist is really weird. Not all code written benefits from async nor even requires it. Running single threaded, sync programs is totally valid.
The function coloring problem represents multiple complaints. I disagree that the propagation of async makes the sync case irrelevant. In the frontend, receiving a promise has completely different implications on loading states. In the backend, I usually try to separate side-effects from pure functions, so the pure functions are usually sync.
Because JS is single threaded, fs.readSync will freeze the entire app. The only case where I would find that acceptable is in cli-scripts. But that could also be achieved with nodejs’ support for top-level await. There's perhaps a slight overhead from the Promise being created, but JS-Engines have so many optimizations that I don't even know if that matters. If nothing else is scheduled, awaiting a promise is functionally the same as blocking. Even in rare cases where you do want to block other scheduled events from running, you could achieve that with an explicit locking mechanism instead.
You could argue that filesystem access is fast so blocking everything is fine, but what if the file happens to be on a NAS somewhere?
'readSync' does two different things - tells the OS we want to read some data and then waits for the data to be ready.
In a good API design, you should exposed functions that each do one thing and can easily be composed together. The 'readSync' function doesn't meet that requirement, so it's arguably not necessary - it would be better to expose two separate functions.
This was not a big issue when computers only had a single processor or if the OS relied on cooperative multi-threading to perform I/O. But these days the OS and disk can both run in parallel to your program so the requirement to block when you read is a design wart we shouldn't have to live with.
The application in question is frozen for that period though, that's the wait they're referring to.
Even websites had this problem with freezing the browser in the early AJAX days, when people would do a synchronous XMLHttpRequest without understanding it.
he was referring to fs.readSync (node) which has also has fs.read, which is async. there is also no parallelism in node.
i don't see it as very useful or elegant to integrate any form for parallelism or concurrency into every imaginable api. depends on context of course. but generalized, just no. if a kind of io takes a microsecond, why bother.
Sync options are useful. If everything is on the net probably less so. But if you have a couple of 1ms io ops that you want to get done asap, it's better to get them done asap.
You can definitely have a race condition in JS. Being single-threaded means you don't have parallelism, but you still have concurrency, and that's enough to have race conditions. For example you might have some code that behaves differently depending on which promise resolves first.
Sure, but concurrent != parallel. You can't have data races with a single thread of execution - a while loop writing i=0 or i=1 on each iteration is not a data race.
Two async functions doing so is not a data race either.
You should really look up the definition of race condition; it has nothing to do with parallel processing. Parallel processing just makes it harder to deal with.
You're mixing up quite a few somewhat related but different concepts: data races, race conditions, concurrency and parallelism.
Concurrency is needed for race conditions, parallelism is needed for data races. Many single threaded runtimes including JS have concurrency, and hence the potential for race conditions, but don't have parallelism and hence no data races.
Concurrency with a single thread of execution runs with complete mutual exclusion, so no "pure" single threaded concurrency is definitely race condition free.
What we may argue over (and it becomes more of a what definition to use): IO/external event loop/signal handlers. These can cause race conditions even in a single threaded program, but one may argue (this is sort of where I am) that these are then not single threaded. The kernel IO operation is most definitely not running on the same thread of execution as JS.
I think I have been fairly consistent in the definition of a data race as a type of race condition, where a specific shared memory is written to while other(s) read it with no synchronization mechanism. This can be safe (most notably OpenJDK's implementation is tear-free, no primitive or reference pointer may ever be observed as a value not explicitly set by a writer), or unsafe (c/c++/rust with unsafe, surprisingly go) where you have tearing and e.g. a pointer data race can cause the pointer to appear as a value that was never set by anyone, causing a segfault or worse.
This composability was also a defining feature of Launchbar.
I loved it, but eventually found that Raycasts approach of having predefined plugins for each use case is more performant , discoverable and usable.
Kinda like how the unix philosophy was beaten by integrated full-stack applications.
* since anything can be composed, everything must be in the same search index. This slows down the index, and means you need to sift through more irrelevant results.
Narrowmindedly worrying about syntactical differences is contributing nothing to the conversation. The point is relinquishing control of state to the framework (be it via props, hooks or @State), and drawing the UI as a pure representation of whatever the framework tells you. Hence ui = f(state). This gets you a metric ton of advantages, which is why every modern framework is doing it.
Classes, by themselves, are inadequate as containers for state unless that state must only exist in memory and never be observed, synchronized or serialized. You can attempt to patch that with decorators, ORMs or whatever, but now you're doing the same thing as React is doing with functions.
reply