The problem with complex systems is that you can be dead long before you realize you're dead. Things can continue to seem "pretty good" for a long time just from the inertia of past good decisions and built-up infrastructure. You see some fraying or cracks but everything looks fundamentally sound, until it isn't.
Apple's inability to deploy a new UI framework that's better than the previous one is troubling. This is the company that shifted from Toolbox to Carbon to Cocoa and each step was better than the last. Could Apple build MacOS X and Cocoa today if they didn't already exist? Microsoft's two decades of failure to ship a true successor to Win32 suggests that Microsoft (at least, the operating system side of the company) died a long time ago. I wonder if we'll say the same thing about Apple 10 years hence.
7 Years of release on Swift UI, 12 years for Swift.
This isn't 7 years of development, but 7 - 12 years after release. And to most these past 7 - 12 years are an ongoing beta development. It is unfinished, unpolished with no end in sight. And I have been extremely critical since the beginning.
But the problem runs much much deeper. It isn't the technology that is the problem, not the devs who are making it. It is the person making the decision as to WHY this was allowed from the get go. WHY this was allowed to be released, or even before all that WHY resources for development of these ideas were allowed in the first place.
For every yes that are a thousand no. Instead a lot of Apple's software development can be summarised as resume or KPI bonus driven development. It is not as bad as Google and Microsoft, but it is clear Apple have this problem a well.
If you look back into Jobs era of Apple software, ( and not just software ) Apple manage to have done most things 2x better with half the resources. The whole Apple now is bloated. And yet everyone is chanting Craig Federighi all the way.
And it is funny because for the vast majority of these 7-12 years I was the only few on HN and Twitter that was extremely skeptical of it, to the point I gave up writing ( or ranting ) about it before majority Swift and Swift UI developers negative sentiment emerged.
And I have often asked the same question every single time, what if they just spend one fifth of the resources to iterate and improve C / Objective-C and Cocoa. All the resources on Swift and everything adjacent to it could have been better spent somewhere else.
I have high hope for John Ternus, hopefully he gets his political game right and manage to change course for Apple software somewhere down the line.
I agree with you. Swift should have been a modernized Objective-C. It should have kept the best parts of it, which made it a joy to use, and leave the archaic and the weird things behind.
it would have been a great language, meanwhile they decided to throw everything out and created a language that it is overcomplex, and it is failing to gain any traction outside iOS / Apple's ecosystem. Basically, if you weren't required to use it, nobody would, which makes it a failure.
SwiftUI, should have been a rendering template/library integrated with UIKit. Also, cleaning up some of the UIKit syntanx and framework (simplifying it), and use SwiftUI as a template for UI, would have been ideal.
SwiftUI instead was pitched as replacement, yet it is not near as capable as UIKit, and it is not feature parity even 7 years later.
Just bad leadership by Apple in this case. (probably many of these decissions were 'promo / resume padding driven' as it happens in many large companies).
While reading this comment, two of Steve Job's line pops up in my head. I think it was the Apple Park opening, and Steve response to insult in WWDC 199x.
> "We need to be true to ourselves, and remember what is important, that is what going to keep Apple Apple, is if we keep us, us."
> "You have got to start with the customers experience and work backwards to the technology.
Right now it feels a lot like Apple without Steve Jobs the first time round. On paper there are a lot of cool tech being worked on. But in the end it was tech from NeXT that really shines.
And a lot of these tech don't actually benefits the customers. If the choice was a language and framework that is slightly harder to developers but brings overall higher quality Apps because the barrier is higher. Compared to a language that wants to replace Assembly to Javascript while being easy to learn like Visual Basic and for everyone to code. I would much rather pick the former.
Edit: I suddenly remember I also submitted a Core Animation introduction video from Steve a while ago on HN [1]. 20 years later we have gone backwards on a lot of things.
I remember this video, from way back. Then, it was impressive, but it also felt logical that machines of that era were able to do it. So it wasn't magic. Just good engineering. Now everything is jank & stutter and my 64GB 25 times more powerful machine just refused to play back a YouTube video in my Safari tab since I have too many windows open.
My iPhone 16 Pro is the jankiest user experience I’ve had on a new iPhone ever. On my Mac the settings app pauses and stutters. Jobs would have thrown things if he ever saw this.
I knew Apple was lost when they dropped the “Home” button, just after spending a $1B to build a Palo Alto headquarters in the shape of the “Home” button.
If you dont believe me… nobody is stopping you from testing various things on the same model?
And if you dont have it already… then what could I possibly say that would motivate you to buy a used one to do the testing to confirm?
Edit: And even on my ipad pro m2 doing literally nothing other than swiping to the app library often causes a visible stutter and lag for hundreds of miliseconds… so if you really think it’s flat out impossible to find consistently slower things on the much more complex macos… idk what to say.
On the contrary, they care enough about facts not to take a random, implausible and unsubstantiated claim at face value. Please don’t resort to Bulverism.
I just don’t care that much either way about what others think if it’s adversarial, even if thousands of HN users come to this comment chain to opine about this or that macbook model.
If you mean my claim that some team in Apple already knows about it, there’s literally no way to prove that to passing readers.
That’s exactly the point. Why would you expect others to even entertain a vague claim like that if you give them no reason to find it plausible? “Just test it yourself” (test what?) i.e. “prove me right yourself” is not going to be very successful.
… Or perhaps I too would be interested to know what you allege is faster on your 2019 MacBook? Or was it only ever meant as “random noise opining” and nothing more?
> Swift should have been a modernized Objective-C. It should have kept the best parts of it, which made it a joy to use, and leave the archaic and the weird things behind.
That's what Objective-Smalltalk is, or rather, what it started out as.
It has now gone further to actually implement Brad Cox's idea of a "Software IC", by taking on ideas from software architecture and metaobject protocols.
> SwiftUI, should have been a rendering template/library integrated with UIKit.
Do you mean it should have used some sort of XML-ish/HTML-ish templating mechanism? For Interscript, I keep looking at that approach, but so far haven't gone down that road (except for HTMXNative, but there it actually is HTML, so...).
> it would have been a great language, meanwhile they decided to throw everything out and created a language that it is overcomplex, and it is failing to gain any traction outside iOS / Apple's ecosystem. Basically, if you weren't required to use it, nobody would, which makes it a failure.
The Objective C at StepStone was completely different than the one at NeXT. It was very primitive. Steve Naroff did pretty much all of the heavy lifting, and turned Objective C into a language that could be used to create NeXTStep, and then OS X, and eventually iPhone.
> Instead a lot of Apple's software development can be summarised as resume or KPI bonus driven development. It is not as bad as Google and Microsoft, but it is clear Apple have this problem a well.
When did the term “KPI” come into use at Apple? I’m aware it was used in management circles decades ago but I don’t remember it being so common say in 2005. The underlying concept is sound, but the problem with abstracting it under an acronym is that it treats KPIs as somewhat fungible regardless of importance. It’s like “lines of code” as a productivity measure all over again.
My opinion is that good old UIKit based on MVC (Model-View-Controller) is simply excellent for building scalable, complex and performant UI's in traditional, compiled languages. The declarative, state-driven UI works wonderfully on the small keynote-demo scale but needs a specialized language to support it on the large scale. Otherwise you waste a LOT of time debugging.
And Swift is simply just not that language. Apple should have just invented a specialized UI DSL to support SwiftUI. And continued to support good old MVC first-class. Explicit Constraints, Lifecycle Clarity and Programmable Control make UIKit apps easier to debug and fix.
SwiftUI should cleanly extracted out its reactive framework as a standalone, public framework for non-UI or decoupled use-cases. That would have helped in fixing a lot of issues.
It only worked that way for Steve Jobs, because under him, as it was at NeXT as well, either you dedicate your life to the cause, or go work elsewhere.
I think this opinion is heavily shared by Swift developers now, but the messaging every WWDC is always "Swift and SwiftUI is the best way to build apps for Apple Platforms", etc.. If you have to keep telling everyone what they don't believe is true, it's a sign there's a problem.
It feels like someone with a lot of organizational power is disconnected from the pulse of the community. SwiftUI is undeniably clean in a lot of ways, it presents beautifully and fits on slides well, but that matters less and less, and this all wasn't really working out even before LLMs disrupted things.
But they do quietly drop or fix them, covertly acknowledging they were mistakes. Often with this messaging: "we have this new shiny thing that is even better than the old shiny thing (that was really a turd)".
Remember "garbage collection"? Or "modern syntax"? Or CocoaJava?
And with hardware they had their "come to Jesus" moment a while ago. And then hit it out of the ballpark with Apple Silicon.
CocoaJava was when they were not certain devs educated in C++ and Object Pascal would ever accept Objective-C.
Garbage collection is still there, regardless of the marketing message without fundamentals from CS theory of automatic memory management algorithms, because they need to blame something else other than themselves, Apple does no wrong.
> They actually went all in on CocoaJava. I was there for the WWDC.
Of course they did, until they saw the Objective-C adoption numbers were high enough.
> "Garbage collection is deprecated in OS X 10.8. Use ARC instead—see Transitioning to ARC Release Notes."
ARC is garbage collection, of course mighty Apple won't acknowledge that, because it doesn't suit their marketing, and they are to sell ARC after the Objective-C 2.0 conservative GC failure, given the underlying C semantics.
So they need to sell ARC as the great saviour, so much better than "GC".
> until they saw the Objective-C adoption numbers were high enough.
Nope. Until they saw that (a) CocoaJava was a complete dud, not just technically, but also in terms of developers buy in and (b) Cocoa/ObjC was accepted well by a large part of the dev community.
> > "Garbage collection is deprecated in OS X 10.8. Use ARC
> ARC is garbage collection,
In terms of the Apple ecosystem, "Garbage Collection" refers to the failed attempt to introduce a tracing garbage collector. Apple/OSX always had reference counting (introduced by NeXT pre-acquisition with Foundation in EOF and later in OPENSTEP 4.0), which technically is also a form of garbage collection, but again in this case the terms are distinct.
> So they need to sell ARC as the great saviour, so much better than "GC".
Again, in the Apple ecosystem, "Garbage Collection" always referred to the tracing collector, even before they had to abandon it due to it not working.
And ARC is markedly better than their GC, but only arguable and at best marginally better than the reference counting they had pre-GC, and in some significant sense worse. Which is why I generally don't use ARC. For my style of programming the benefits are minuscule and the drawbacks real.
No. Just that within the Apple ecosystem, the terms were used in a specific way.
Apple GC is a form of GC. There is no redefinition going on. It is correct.
However, it is ridiculous to say "we will replace garbage collection with garbage collection" and it is too cumbersome to say "we will replace reference counting garbage collection with tracing garbage collection".
Both "garbage collection" for the tracing garbage collection mechanism and "reference counting" for the reference-counting garbage collection mechanism are correct uses of terminology.
Using these shorthands instead of either the cumbersome complete terms or the confusing other shorthand is perfectly fine.
Although admittedly some Apple zealots started insisting that the Apple shorthands were the correct terminology. Or that programs that were broken by the broken GC had always been broken. Or that what Apple calls "MVC" is actually the correct definition of MVC when it is not.
If you want to see this behaviour dialed to 11, check how Microsoft management talks about WinUI 3.0, and the harsh reality of its sore state of development experience.
It would be great if it was half as bad as SwiftUI.
It’s a seductive idea, but it displaces who is responsible for the poor framework development onto users of the framework.
It’s easy to say they were just trying to be trendy and thus the real flaw was the trend is bad and the fault of worse devs than us (the young web devs mentioned)
The thing that obviates that is the trendy stuff works, yet, SwiftUI doesn’t.
(source: I wrote ObjC/Cocoa as early as 2006, and switched to Flutter as my primary dev kit some years ago: it simply doesn’t have the performance issues mentioned.)
No, they did not change the renderer because of Apple. That is a complaint about Flutter, the iOS-aping widget set can't stay up with current iOS, and I don't think they have a real solution yet. There's something about pulling out the Material UI library from Flutter itself that's supposed to help (I don't quite understand why, modulo "we can make a focused team work in a package instead of in the big ol' framework and that'll be easier")
Generally, it is not young web devs fault that SwiftUI exists, and certainly not their fault it still has serious issues 7 years in. The things named in the article as not-working in SwiftUI do work in fine in the others.
Cocoa and Objective-C are both great, but I think there's one huge problem that they never managed to solve well: responsive layouts.
Classic iPhone apps have fixed layouts, and classic Mac apps have a big dynamic document in the centre with mostly fixed toolbars around the sides, and dialogs with fixed layouts. Making a really dynamic layout with OS widgets is a huge pain, so most apps sidestepped it.
As Apple gradually added more and more slightly different sizes of iPhone and iPad, making good UI layouts got harder. Even handling screen rotations is a huge hassle! If iOS had proper support for resizing from the start, screen rotation would be trivial, just another window resize.
Xcode had Interface Builder and autolayout, but I found those to be disastrously fiddly and unusable. Maybe some people like them?
HTML has many problems of its own, but it does have good support for responsive layouts. CSS isn't perfect, but it's vastly better than anything you can find in macOS, iOS, Android or Windows.
It seems to me that SwiftUI was trying to tackle two problems at once: React-style declarative UI, and responsive layout. Those are both good ideas, but trying to tackle both in a single uber-framework and deprecating the lower layers was too ambitious. As somebody said elsewhere in this thread, SwiftUI could have been decent as just an optional helper on top of the existing UIKit / AppKit.
Well, you can still have breakpoints, and switch between different structures for e.g. mobile vs desktop, or landscape vs portrait.
But I don't think you can cover all requirements with a set of fixed layouts, they do need to be somewhat flexible. Even if all screens were the same size, you'd want to be able to boost the font size for accessibility, and that essentially means scaling the whole UI.
And the slightly different sizes of iPhones are not really that problematic with something like autolayout.
I think something like flexbox is a better fit. There are tradeoffs in each approach, but I usually find a local, modular, bottom-up approach easier to work with than global constraint solving, even though it seems in principle like it should be nice to be able to say "keep this button here in relation to this other button". As you add more constraints like that your layout slowly turns to mush and doesn't actually resize nicely. (Edit to add: I'm probably conflating a few different generations of iOS toolkits here, I realise autolayout is somewhat separate from constraint-based layouts.)
I don't think it's a coincidence that most other UI toolkits have added something like flexbox (including iOS) -- it's not perfect but it fits how people generally think about UIs. Was HTML/CSS the first major UI framework to use flexboxes? That's how I remember it, but maybe it was copied from somewhere else.
> But I don't think you can cover all requirements with a set of fixed layouts, they do need to be somewhat flexible. Even if all screens were the same size, you'd want to be able to boost the font size for accessibility, and that essentially means scaling the whole UI.
Also, you want to localize apps, and different languages have /very/ different amounts of text for the same UI.
> And the slightly different sizes of iPhones are not really that problematic with something like autolayout
It can be problematic regardless of the technical solution, though generally I have to agree with the loudest voices on this thread: SwiftUI is a load of rubbish, UIKit and autolayout (and Interface Builder) was better.
Regarding problems with all layouts: I've got a 2022 model iPhone SE, so smallest screen, and every so often run into an app which just doesn't handle the screensize right. I've seen this happen as a developer with both UIKit and SwiftUI, it's just the type of bug you end up seeing is different.
Good points. It's just that responsive layouts have not been fixed anywhere. Also on the web, it is way too hard. How often don't you see a box floating over some background photo and then covering the focal point, e.g. the face? Sure, it can be done, but the permutations of testing are simply too large for mere mortals.
Multiple 'artboards' for screen sizes/ratios are the solution, which basically just means swapping out a .nib in Interface Builder. Better to just make it explicit.
The underlying problem is the programming industry completely resisting learning the lessons of Objective-C because it doesn't like them. It didn't like them when NeXT tried selling it, and it doesn't like it now.
You can't have Cocoa without Obj-C, or at least a langauge and runtime with that philosophy.
Apple software has been on a clear decline for a seriously long time, and it's propped up almost entirely by them pulling off Apple Silicon combined with how Microsoft have somehow been even worse.
As someone who hasn’t used the language, I’m curious about what the valuable lessons of Objective-C are that the programming industry is resisting learning.
Weak, dynamic typing. Dynamic method binding at runtime (method calls really are message-sends). 'Traditional' object orientation with inheritance (although delegation and dynamic mixins are also core concepts, and the class hierarchy and method lists can even be manipulated at runtime). Nil is allowed anywhere and messaging (calling methods on) it is a no-op. Heavy use of 'notification' posting and subscription.
All of these are unfashionable nowadays, but they’re fundamental to Obj-C (some to the structure of the language itself, some just as idioms) and to the design of AppKit and UIKit.
Now, the fashion is:
Strong typing. Static binding at compile time with no runtime modifications to the type hierarchy. If OO is used, there should be minimal inheritance. Nullability is strictly defined in the type system, and acting on null objects causes, at its most forgiving, an exception, and at its least forgiving, program termination. Notification-based systems may be the outlier here (still in heavy use), but even they are often frowned upon for being too 'loose' and unanalyzable, at odds with the ideals of static typing and static binding.
Many, though I have to start with this disclaimer: I still don't fully understand why Objective-C is such a sweet spot.
One very important one is that, empirically, it showed how much of what we think we know about language design is ... shall we say ... "incomplete".
After all, Objective-C is a car-crash of a language: take some Smalltalk and jam it into C. Done. How can you write software with this? And yet NeXTStep and Cocoa/CocoaTouch, arguably the most elegant pieces of OS-level/UI-framework software ever, were written in Objective-C. And not despite of it, but because of it.
From a safety standpoint, it's hard to see how you can get worse: all the static type safety of Smalltalk (none) combined with the memory safety of C (none). And these interact.
And while it certainly is possible to use it very, very badly, in practice I haven't seen the horrors that we are supposed to get.
As an example, we got the same level of improvement from doing an Objective-C → Objective-C rewrite with Wunderlist (from WL2 to WL3) that others claim for Objective-C to Swift transitions.
Also, dynamic messaging is said to be slow, yet Objective-C programs consistently outperform the much more static Swift ones.
And of course, we all know that to do dynamic OO, you need a large runtime and better yet a VM. But it turns out that you can get much if not all of it with a tiny sliver of an extension to portable PDP-11 assembly language.
This! I remember reading 'Cocoa Programming for Mac OS X' by Aaron Hillegass and being fascinated by it. Coming from Java, C, doing some DSP code, web work, Objective C and Cocoa at first seemed just utterly alien and a bit wrong.
Message passing, these loosely coupled 'delegates', a mixture of hardcore C, needing to do memory management but also just passing objects around in the runtime... messy, weird... but in the end the right solution to make flexible but performant software. I learned a lot from it. I should dive into the history of NeXT one day.
The problem with ObjC was mainly syntax, bolted on over many years on top of a C core. It was grown rather than designed, and it shows. But there's nothing wrong with the runtime.
Swift should have just been a much improved syntax over that same runtime. It would have avoided so many headaches. For one they wouldn't have made the awful decision to have return-type overloading and completely ruin the typechecker's performance in the process.
SwiftUI should have unapologetically been a convenient, reactive wrapper over UIKit and AppKit, rather than treating them as deprecated implementation details that devs nonetheless keep having to drop down into.
These decisions come down to ego, of wanting to do away with the old and carve out your own legacy separate from it. Nice if you want a promotion but not so nice if you're trying to make a long lasting ecosystem for developers to build upon.
At least Swift and SwiftUI have a reasonable interoperability story with the past. Can't say the same about Microsoft's graveyard of UI frameworks.
Gotta take issue with the characterization of “bolted on”. That’s a design feature and it’s huge. It made interoperability trivial. You can write C, C++, and Objective-C within the same source file.
Bolted on wasn't a value judgement, it was literally bolted on top, it is a strict superset (unlike C++, which is also bolted on, but poorly, since it partially breaks compatibility).
The syntax growing over time is a real issue though. Every time I have to write __bridge_retain I get hives.
i love swift, but i can't help but feel the same... after all this time we still can't get good error messages or fast incremental compilation; it just feels like we are boiling the ocean to get largely the same features but handled by the compiler now instead of the runtime (which has a lot of value, but i feel theres a balance to be had there idk)
if they had really just done objective-c without the c like, the original moniker said...
> SwiftUI should have unapologetically been a convenient, reactive wrapper over UIKit and AppKit,
I am also working on a UI "framework" I call InterScript that is (for starters) a wrapper over UIKit and AppKit, and basically solves what I perceive to be the biggest problems of Cocoa:
1. UI specification using object literals. This makes UI-creation very compact and readable. The following example (mostly) reproduces a SwiftUI "Form" example from Apple documentation:
There are actually even better ways of accomplishing this, but this should give you an idea.
2. Better communication between model and UI by using the support in the language for polymorphic identifiers and dataflow. Because dataflow is in the language, it can be expressed succinctly without making it hidden magic like in SwiftUI. With dataflow and polymorphic identifiers, you also get a dataflow-constraint mechanism similar to Cocoa Bindings, but again with proper support and less magic.
If you were to build a system with no requirement for backward compatibility with cocoa or macOS, or even knowledge from such a thing existing, we have still moved forward three decades from not just NeXT, but the computing industry that NeXT was built to serve.
I'd assume their requirements would be drastically different, and the system they created would be drastically different as a result. The requirements aren't even easy to hypothesize, since iOS would never have existed.
Yes, I would imagine so. I don't think the result would look exactly like what it does now since needs have changed, but I can imagine that they could do it.
This type of story about management getting rid of the old guard is so common and typical, it remains one of the most frustrating aspects of this industry.
In the “enterprise scene”, this sort of purge strategy is likely linked to a big percentage of money wasted and sometimes total failure.
Why do managers keep making this mistake? “Legacy” is only a bad word in IT.
:-(
He left after the Apple acquisition in 1999, then came back around 2009. During the time he was gone was when Apple did their best work (Snow Leopard and iPhone).
I think the main issue is the functional approach. Having a functional approach to state can be quite elegant, but ultimately the computer does not do functional. You have to implement a lot of plumbing to have stuff that is a bit performance (clojure), or just add a veneer of functional over what is essentially a normal state machine (emacs).
React works well, because it's only an abstraction over the real DOM. React only handles your app state. But the DOM mechanism is still very performant and very much imperative. But I don't think something like React would work well in a mobile app, because the UI tree is often very simple on iOS and macOS.
UI is also very much not functional, and in fact the lack of progress in UI the last 30-40 years can largely be traced to trying to create UI with procedural/functional programming languages, an instance of linguistic-architectural mismatch.
Further reading:
Programs = Data + Algorithms + Architecture: Consequences for Interactive Software Engineering -- Stéphane Chatty.
"A view is a (visual) representation of its model. It would ordinarily highlight certain attributes of the model and suppress others. It is thus acting as a presentation filter."
View and model are related, but neither is procedurally dominated by the other. The view is not a subroutine of the model, or vice versa.
They are related entities that communicate in order for the view to function as a representation of its model to the user, and for the model to be manipulated by the user.
To get the details I really recommend the Chatty paper. It is a bit hard to read, but delivers the goods.
That's pretty much where immediate mode GUIs and to a less extent React came from. (Though for React the provenance is probably closer to the HTML web-app: send request - update model - return HTML with complete and completely new UI.)
The difference is that for most games, throwing away the complete rendered graphics and re-rendering from the world model is often the right approach.
For most UIs, it isn't, unless they really are very close to video games, for example mostly passive feed readers, video players etc.
> The difference is that for most games, throwing away the complete rendered graphics and re-rendering from the world model is often the right approach.
Well, that is what "retained mode" GUIs (i.e. those using control/widget trees and such) do too.
The immediate mode GUI to me always felt like an approach came up by people who do not really want to bother doing GUIs. In terms of games it is like mixing logic and presentation in a game like, e.g.
int x, y;
while (game_running) {
draw_sprite(player, x, y);
if (key_down(LEFT)) x -= 1;
// etc
}
basically like in a game that doesn't have a world model with entities etc, just draws sprites directly, handles input directly, etc. Which is very simple for simple stuff but doesn't scale as complexity increases.
Same with immediate mode GUIs and i don't think it is a coincidence that the more complex an imgui gets, the more it starts looking like a "retained mode" GUI and the gnarlier the code becomes.
This is an optimization that some GUI systems do but it is not inherent to "retained mode" GUIs, does not apply to all systems nor all systems use it. I remember GUI systems even from late 90s/early 2000s composing their widget trees afresh when needed (and in games it was pretty much always the norm for GUI systems to work like that after engine started taking GPUs for granted). Some would limit the refresh to widgets overlapping the damage region reported by the OS (if a compositor was not running, for OSes since Vista) but even that is just an optimization.
On the other hand immediate mode GUIs are inherently like that because that is their core premise.
At each point you reify the result into an artifact that is everything the next stage needs. This has a couple of nice properties. One property is that everything is deterministic and testable. Another property is that after the pack and apply, everything is now embarrassingly parallel. Another good property is that you have an artifact that the accessibility people can latch onto before you bury it under pixels.
That would be a very functional way to deal with GUIs.
However, we continue inheriting properties and single main threading everything like we're still on 33MHz machines with 8MB of RAM.
You are applying a one way arrow to events where in real life events do change state and can completely change UI (and functions) itself. Which makes imperative always the superior mode.
What you described works only in super simple scenarios. Whink Web 1.0, when Javascript was used for basic form validation at best, and didn't change DOM that much.
1. What does that even mean? I don't see a UI in there at all, at best some graphics (Render).
2. That's not "functional". If anything, it looks like a pipeline, so dataflow. But then again, see (1)
3. Not sure why reification, that is turning things into objects, is functional to you. Reification, that is turning things into objects, is object-oriented.
4. MVC was actually created on 5.8 MHz machines with 128KB of RAM (including the display buffer). And still works beautifully today.
Functional is all about reification. You take a set of things in, you apply/map/collect/fold/whatever, you eject a set of things out. That is 100% functional--every time you supply the same inputs you get exactly the same outputs. The point of functional programming is that you avoid mutation and hidden state.
And, um, side note: dataflow programming is almost always considered functional programming.
Object oriented, by contrast, is all about hidden state and mutation. I send a message or call a function on that object over there and fingers wiggle and magic happens. But I don't know what or when or even if it actually happened.
And increasing evidence suggests that MVC doesn't work beautifully today because it doesn't parallelize, and it scatters state between uncooperative things. Just ask every single GUI that exists--every single one hangs because somebody made an oops and put too much work on the single, blessed primary thread. Or they have janky scroll. Or resizes leave gunk on the screen. Or your system flashes and jiggles because it's too busy reflowing everything in the universe. Or ...
You obviously have some very non-standard definitions at work here. apply/map/collect are just higher order operations, they have nothing to do with reification, except that you need the functions that are arguments to be first class.
> dataflow programming is almost always considered functional programming
That turns out not to be the case. Dataflow programming shares some aspects with functional programming, they are not the same at all.
> Object oriented, by contrast, is all about hidden state and mutation.
That also turns out not to be the case at all. Heck, there were even object-functional programming languages.
> And increasing evidence suggests that MVC doesn't work beautifully today because it doesn't parallelize,
What does MVC have to do with parallelization, in your humble opinion?
Famous UI = f(Model) is oversimplification that was sold in slides. Real “functional UI” frameworks implement UI = f(Model, UIState) where UIState is scroll and cursor positions, view pool for virtualization, rendering caches, etc. USState is mutable and managed by the framework and the rendering engine (e.g. React + DOM, SwiftUI +
UIKit + CoreAnimation). I don’t see a problem with functional approach as in React. I do see a problem with understanding of how UIState being managed between framework, ui library and rendering engine.
Functional doesn't mean stateless. I'd argue functional programming is superior for representing state in user interfaces. For a success implementation, see Jane Street's Bonsai:
> functional programming is superior for representing state in user interfaces
It's always going to be slower than using something imperative. Trying to process the entire world's state for the sake of purity can feel elegant but it isn't free. And the complexity that gets added to make things performant is worse than just accepting that UIs are going to require you to jump around the tree and modify state.
Every time I go through the trouble of understanding the latest web technique (React, Elm, Signals (the newest solution), etc.) to deal with state management and the DOM, I end up walking away disappointed. There's nothing new in them that you can use to improve what we've been doing for ages in native GUI toolkits.
And to jump back to the original topic, yes, Cocoa was pretty decent, and SwiftUI while nice in many ways tries to Reactify native macOS development and made it worse. And it made Swift incredibly more complex and worse in hindsight.
The goal of the reactive/declarative approach was never to be more performant than imperative code. The goal is to more easily build UI that is performant enough and functions correctly. With imperative UI code it is incredibly easy to forget an edge case in your update logic.
MVC is not bad, but it is not a silver bullet. Calling MVC an ultimate solution to UI is oversimplification. Just looking at the steps you listed I can ask:
How do you collect all notifications on step 2 to fire them on step 3 such that UI does not re-render itself too much? E.g. updating a title of each item in a list of 100 items should not trigger 100 renders. Or 100 layout calculations (which I think is harder to avoid).
How do you deal with situations where on step 4 UI triggers an event that your model happens to listen and the cycle repeats while killing performance?
Because you rely on events how do you avoid “event hell”? That is, a situation when an event handler triggers a change that triggers another event handler that triggers a change and so on. Sometimes it is scrolling or typing, sometimes it is parts of the model subscribed to each other bubbling events to UI.
I never claimed MVC is a silver bullet. Just that it solves "... incredibly easy to forget an edge case in your update logic."
> UI does not re-render itself too much?
Glad you asked! In my Blackbird reference architecture (which is an instance of MVC), I use a coalescing queue to capture the updates. The coalescing is two-level: first, simple duplicates are weeded out. Second, if the update queue gets very full, it becomes coarser-grained, and weeds out duplicates based on that coarser grain. This has multiple steps of grain up to "just re-render the whole UI". Worked like magic in Wunderlist. Except it wasn't magic at all and very simple, inspectable and tractable.
> step 4 UI triggers an event that your model happens to listen
That's not allowed in MVC.
> Because you rely on events how do you avoid “event hell”?
I don't "rely" on events and there is no "event hell". Events are only used in the M→V communication part and there are no subsequent triggers, because the only event is "the model has changed", with an optional payload specifying which part of the model. Important: it must not contain the data that changed, this the view has to fetch from the model once it processes the update event.
Since the only event used is "the model changed", the view cannot ever be a source of those events, so no "event hell".
How do you handle UI state vs. underlying data (model) state, and dependencies between them? By UI state, I mean things like scrollbar position and selection state. When displaying a scrollable and selectable list of items, then for example when the number of items changes, the selection may need to adjust, and the scroll position may need to adjust. Depending on which items are added or removed (or reordered), the selection and scroll position may need to change differently for the apparent UI state to look stable for the user. If only the model is changed, a previous UI state like selection or scroll position may become invalid in relation to the new model state. Who updates the UI state accordingly to make it valid again? In the general case, application code needs to be involved in choosing the desired valid UI state when the underlying model state changes. How is the corresponding application code prevented from triggering further events?
When you have stateful view objects, these stateful view objects maintain the view state. When updating themselves with new data due to a ModelDidChange notification, they take care of reconciling their current display state with the underlying model state.
> When displaying a scrollable and selectable list of items
So for example an NSTableView or NSCollectionView. I personally use a subclass that interacts directly with a table representation, meaning a lot of the glue code that Cocoa(Touch) requires disappears.
> Who updates the UI state accordingly to make it valid again?
Always the view. Who else?
> In the general case, application code needs to be involved in choosing the desired valid UI state when the underlying model state changes.
How so? The view is always a reflection of the model data. Whether that is a "change" is actually mostly irrelevant, even though the notification is called ModelDidChange in my case. In Smalltalk MVC it is the #changed message. It means "you are out of date, please make yourself reflect the model".
This same mechanism also handles the model being changed by some other party without any further code. "The model has changed, please update yourself to reflect the current state of the model". That's it, modulo optimizations.
> How is the corresponding application code prevented from triggering further events?
Model code isn't involved. A ModelDidChange event is only triggered when...er...the model changes.
That said nothing prevents you from manually invoking the ModelDidChange notification, just like nothing prevents you from calling abort(), running an infinite loop, creating an unbounded recursion or reading from /dev/random until it is exhausted ...
Doing it by accident, though, is very hard, because it just isn't part of the programming model.
>The coalescing is two-level: first, simple duplicates are weeded out. Second, if the update queue gets very full, it becomes coarser-grained, and weeds out duplicates based on that coarser grain
This is not about duplicates. For example, sync updates 100 items in a list changing their titles. Items are bound to a list in the UI. Thus, 100 unique title update events triggered.
>Events are only used in the M→V communication
I don’t understand. Button clicked -> model change -> view update -> new event triggered -> model or view updated again … This is not something one would code on purpose, but often an attempt to create relationships between view. Like a custom layout code. Might not include model at all, just views being updated in an event handler trigger more events and more updates to views.
> For example, sync updates 100 items in a list changing their titles. Items are bound to a list in the UI. Thus, 100 unique title update events triggered.
Those "updates" go in the queue. When the UI gets around to updating itself, it looks at the queue and invalidates all the UI elements that refer to the model items in the queue.
It then updates those elements, using the coarsening to update larger elements in bulk if that becomes better.
> Button clicked -> model change -> view update -> new event triggered -> model or view updated again
Once again, that is not allowed. View updates are not allowed to trigger any events in MVC. A model → view update updates the view. That's it.
The only event is "model changed", so it also doesn't make sense for the view to generate those events.
I can only say how I did this in the Azul GUI framework[1] (note: not production ready yet), which may be close to what you're describing. So in Azul, you do this:
So, there's no "automatic" re-render, a callback has to return "Update.RefreshDom" or "Update.DoNothing" (default).
Now to your questions:
> How do you collect all notifications on step 2 to fire them on step 3 such that UI does not re-render itself too much?
Diffing, and then caching very aggressively. The click causes the model to re-call the layout() fn to return the entire DOM, however, there are ways to make this step very fast (arena allocation / no allocation). Then this gets diffed with the previous DOM state and the framework internally reuses everything it can (with user providing keys for list items, like React does).
> How do you deal with situations where on step 4 UI triggers an event that your model happens to listen and the cycle repeats while killing performance?
Azul has a "max recursion depth" of 5 and then just throws an error (infinite cycle). So, it will invoke all the relevant callbacks for a frame, then "sum up" all of the Update enums (i.e. one callback returned RefreshDom -> now we need to repaint).
> Sometimes it is scrolling or typing, sometimes it is parts of the model subscribed to each other bubbling events to UI.
Scrolling, selection, typing, etc. are handled by the framework. To make something editable, you need to set "contenteditable=true" on the Dom node (like on the web). Then, on text editing (which can also come from IME, a11y input, copy-paste), you get a "text changeset". The callback can then "reject" the changeset or allow it (default, since you already set contenteditable before).
Azul has a "dual update pattern" for performance here, i.e. the DOM itself is immutable until the next layout() call, however for "quick edits" like dragging a node you obviously don't want to call layout() again and construct an entire new DOM tree. So there, you just (conceptually, don't know the current API for this):
def on_div_dragged(data, info):
mouse = info.get_window_state().mouse_state
info.set_css_property(info.get_hit_node(), "transform: translate(%s, %s)", mouse_state.x, mouse_state.y)
# store in data model or node if necessary
data.user_mouse_pos = mouse_state
return Update.DoNothing # no re-render here
So, if another callback fires in between, the data model is still properly up to date. Azul also aggressively reconciles focus, scroll position, selection, text cursor position, etc. But Azul does not allow "one event auto-triggers another" like SolidJS does, it looks nice on a slide deck and then is a pain to debug Rube-Goldberg state machines.
This also works for text input or updating images (i.e. you don't need to call layout again on text input). Update.RefreshDom is for "larger / structural" changes, i.e. something like a route switch in a SPA-style app. Azul tracks the text cursor position by diffing the actual text, so the user code doesn't have to track the text cursor and state is preserved during a diff (it can also retain heavy elements).
For large lists, there is a native "virtualized view" DOM node with a callback that is being called "during" layout (after the size of the container has been determined, then the framework asks you to render your DOM, given the scroll position). So, that can be diffed, too. You never render in the DOM more than ends up on screen, so the perf is manageable.
Scrolling and retaining scroll positions inside a virtualized view is still an ongoing topic (not impossible, you just have to have functions to measure the DOM items before you return them, to estimate how much you need to render, and then do the math for "where are we right now, where is the scrollbar, how big is the virtualized view in relation to what we're rendering" - so the framework can set the right scrollbar size and position).
Again: please don't use or post Azul here on HN yet, docs are still slop and undergoing review, API is unstable until I have some apps going, but I just wanted to answer these questions.
Is the UI updating itself automatic or manual? Because if it’s manual, that’s precisely the error-prone part that you’re saying this approach somehow solves - you’ve done the “How to Draw an Owl” meme. If it’s automatic, that doesn’t seem especially different from the React/Redux/Elm/SwiftUI approach (as a sibling points out).
Yeah, M-V-C are all roles, not concrete objects. The C mediates between the input devices and the model, but in practice views can and often do fulfill that role as well. Cocoa views, for example, also fulfill the C role.
Different formulations of M-V-C have the C deal with more complex interactions, with sequences of interactive prompts like wizards.
The update is essentially automatic, and yes: MVC already solved the "problem with MVC" React/Redux/Elm/SwiftUI claim to solve. In 1979.
I like my Controller to be responsible for all the "business logic" so that its all in one place. It's the important part. The View layer is always fairly verbose and full of fluff. Especially if you have a lot of animation and formatting type code.
> I like my Controller to be responsible for all the "business logic" so that its all in one place.
Business logic is supposed to go in the model. All of it. Because it's the important part.
Controller these days can be largely empty.
"MODELS
Models represent knowledge. A model could be a single object (rather uninteresting), or it could be some structure of objects.
There should be a one-to-one correspondence between the model and its parts on the one hand, and the represented world as perceived by the owner of the model on the other hand. The nodes of a model should therefore represent an identifiable part of the problem.
The nodes of a model should all be on the same problem level, it is confusing and considered bad form to mix problem-oriented nodes (e.g. calendar appointments) with implementation details (e.g. paragraphs)."
Conceptually, the UI re-renders itself completely in order to always be an accurate reflection of the model.
That is the #1 job of the view: be an accurate reflection of the model.
And re-rendering itself completely is a safe way to implement that requirement.
However, the UI can also look at the model in more detail and figure out what parts need to change, as long as the effect is the same as re-rendering everything.
And the model can tell the view that specific subparts of the model have changed to make that job easier for the view.
But if it can't figure out the details, the fallback is to re-render the entire view from the model. But not to recreate the view. The view sticks around.
One way of doing this optimization is "damage rects" like Cocoa does. Another are the polymorphic identifiers used in the update queue of Blackbird.
MVC, the controller is the intermediary between the services/data models, and the views.
That still one of the best / simplest way to build large apps. MMVC is just a variation of it, with the models being able to communicate state to views and bypass controller if needed.
MVC, is still one of those 'fundemental as simple as it gets, and it gets the job done' patterns.
User interfaces representation are mostly trees. And with functional programming you basically have Tree2 = f(Tree1). Until f is done you can't do anything really. React has a lot of escape hatches to improve performance, but they are escape hatches, not an endorsement of the architecture.
With imperative programming (and OOP), you only have that single `Tree`, which you update at will. Less elegant yes, but we have modularization to help us there. What Emacs does is to keep that `Tree` as a single mutable object, but have the code be functional, while the results are imperative.
Imo the biggest issue with this functional model (at least in React), is that it handles things like virtualization, async, etc. poorly. Which is kinda ironic, because in a true functional language, it'd be feasible to provide 'a world model' - that is act as if the entire state is always available, and let the engine decide when to evaluate pieces of code - without any effort from the part of the programmer.
Unfortunately when complex state transitions, async, and virtualization enters the discussion, the magic of React breaks, and you have to deal with all that, and also deal with how React's engine handles things under the hood.
Stuff like virtualization (if we're talking about stuff like virtualized lists) is hard not because of React, but because there just isn't any support for it in browsers. React doesn't really help here, but in my experience, it's usually the browser that starts choking on high element counts, not React.
Async is just difficult in general though. It's not really a surprise that most libraries/frameworks converged on similar designs.
I am talking about virtualized lists. And it should be a framework feature. I used to use WPF on desktop, and it had pretty good virtualization support (though the framework in general was more like Angular) - most containers had virtualization support, and you only had to implement the logic on the data source, and they framework created and managed physical UI elements for you, and managed the mapping so it seemed seamless to the user.
React also operates on a virtual dom, there's no reason imo why couldn't they just fake that for you.
I mean, it's not quite that easy. The web is a lot more dynamic than WPF. If you just want virtualized homogeneous lists, there are libraries for that (and grids).
But, once you start hitting things like differently sized elements, search and so on, you start running into platform limitations that you won't be able to resolve in React land.
I know, that's why I said, that when you hit things like that (state that is too big to pull onto the client at once, and/or displayed at once), React's (and I guess a lot of other immutablity-based frameworks') dataflow management stops being magic, and you have to start tending to it.
Which usually means this model loses all advantages compared to simple MVC, or imperative systems, and at worst, becomes another headache as implementation details start leaking.
I guess my point was that it doesn't matter whether your framework/library is immutable/mutable/retained/functional/MVC/MVVM or whatever. You're hitting platform limitations one way or another.
But the rest of your app still gets the simplicity of a declarative programming model.
Imo that's a rather nihilistic take. My personal opinion is that despite ungodly amount of money invested, the web has changed very little from the 'static website +maybe jQuery' days - 90% of the content people consume is static, with very little interactivity, that would be perfectly captured by the 'script' nature of Javascript (think 10 line scripts, like 'if hovered, play live preview of video'). Even that interactivity is mostly driven by data that needs to be fetched from the server.
'Web Apps' are the exception, and are more or less completely alien to the rest of the content, think 'flash games' or 'google maps' - which have become 'wasm games on unity on itch.io'.
Having a mostly static web for the former, and the ability to basically almost whatever would be for the best.
Speaking of Flash, the 'cross section' of the two - websites with tons of interactivity and flashy animation have almost disappeared.
The web of today is an unhappy compromise of mediocrity and low expectations.
It is a generation thing, all the folks that were able to ship those products are now retired, or approaching retirement, and apparently newer generations can only do Electron, React Native and co.
That wasn't the question though. Being able to say it today doesn't mean you will be able to say it in 10 years. Companies which lose their way sometimes rediscover themselves. One example that comes to mind is Apple. Another example that comes to mind is Apple.
In fact I can think of at least three instances (at very different magnitudes) where Apple has dragged itself out of a stupid hole they dug for themselves.
Not just complex. Also "successful for other reasons". And of course the RDF is particularly strong in Apple's case. And these are interlinked as well: they were successful in the past not just despite ignoring feedback/outside advice, but often because of it.
I’m no huge apple fan, but I didn’t really think the 17 was more than a regular boring spec upgrade, which seems fine all things considered. (Currently on an iPhone fucking 17)
Apple's inability to deploy a new UI framework that's better than the previous one is troubling. This is the company that shifted from Toolbox to Carbon to Cocoa and each step was better than the last. Could Apple build MacOS X and Cocoa today if they didn't already exist? Microsoft's two decades of failure to ship a true successor to Win32 suggests that Microsoft (at least, the operating system side of the company) died a long time ago. I wonder if we'll say the same thing about Apple 10 years hence.