Swift Concurrency Roadmap
forums.swift.org
forums.swift.org
Let me ramble a bit here.
Apple’s platforms had always put users before developers, that’s why they were so successful. They’ve built the best user experience by far using nothing but a simple Smalltalk-esque language from the 80s. Look at their frameworks: CoreAudio, CoreAnimation, UIKit... Tremendously powerful, and the best in their league - in terms of possibilities, not necessarily developer experience! Others were shoving garbage-collected virtual machines in their devices, piling abstraction over abstraction, “fluent APIs” (remember those?); meanwhile Apple built an empire with this quirky dynamic language (plus some C++ under the hood), where you had to do manual reference counting as late as 2012! Developers were livid. “How am I supposed to program in this weird language?” “AutoLayout? Why can’t it be just like CSS or something??” But Apple didn’t give a shit, they knew their platform, their frameworks were the best, period. And whiny devs had to adapt to gain access to the richest cohort of users in the computing landscape.
With Swift and SwiftUI we seem to be heading towards a different future, and I’m yet to be convinced that it’s the right one.
I can't even say "best" experience let alone "by far".
Apple is notoriously anti consumer on everything from hardware to software.
I don't know what you are talking about. They had a fun signup screen when you first boot your iPhone. I have 0 ideas what you are talking about for macOS.
I think their primary reason for success was their advertising/marketing.
Yet millions of people continue to happily give them money without being forced to, unlike say Microsoft or Google that you just cannot avoid even if you don't own any of their products.
I was a Windows/PC user for most of my life before I switched to Macs and in my experience Apple has been consistently more consumer-friendly than Microsoft or Google.
In fact my first foray into the Apple ecosystem was an iPad that I bought as a gift for someone, and fell in love with it so much I wanted to develop for iOS and bought my very first Mac for that. So far nothing has made me consider switching to Microsoft or Google.
The only thing I hate about Apple is their poor documentation and lack of official support on their own forums.
It's not like you could leave Apple, you are locked in.
It may be hard to put into words why you feel that way. (which is why your post never mentioned anything about quality)
As mentioned opening the iphone box and giving Apple my personal information felt fantastic. The actual performance and quality was extremely dissapointing.
One could say the same for you, but that won't get us anywhere.
> It's not like you could leave Apple, you are locked in.
I have lived most of my life without Apple and I know how to do it again.
Tell me, how do you avoid Google or Microsoft?
I was happily using GitHub before Microsoft bought it.
I was happily using YouTube before Google bought it and made it worse.
I can't avoid feeding my data to Google even if I don't use any of their services, through their Ads, Analytics etc. and who knows what kind of shadow profiles they build from just my proximity to their other users.
Now, every time I sign into YouTube I have to delete all Google cookies to avoid being automatically signed into Search.
> As mentioned opening the iphone box and giving Apple my personal information felt fantastic. The actual performance and quality was extremely dissapointing.
I've been using the iPhone since the 5S and honestly, every time I have to handle someone's Android (e.g. to show them how to do something quicker than telling them), I want to hand it back as soon as possible.
If you haven't taken a marketing class, I highly recommend it.
As in, you can easily avoid Apple if you don't own any Apple hardware.
Not so easy with Google or Microsoft.
> With Swift and SwiftUI we seem to be heading towards a different future, and I’m yet to be convinced that it’s the right one.
The more things change, the more they stay the same...
TBH Obj-C once you looked past it's ugly syntax is a better developer experience than swift today if your making large-ish apps. Today the debugger is still buggy, build times are still way slower and swift code bases create stutters in unresponsiveness inside Xcode to this day. And it was way worse back in the swift v1-3 days.
TBH I think swift could solve a lot of their build speed issues if they removed type inference beyond a very basic set and brought back fine file grained importing like you have with C and Java. You still get %95 the benefits with a couple of features that most IDEs like IntelliJ with java are shown to be NBD.
Stuff like Flutter & Kotlin Multiplatform might make a lot of this stuff moot although, and swift will be regulated as an iOS compatibility layer and a nicer looking C++
I think you meant Cassowary instead of Haversine: https://en.wikipedia.org/wiki/Cassowary_(software)
i have some feelings myself, but they are badly formulated so i wont say them here...
but im curious, specificaly why do you feel this way?
It’s basically the usual story with post-Jobs Apple: catering for the masses while neglecting the pros - but this time it’s about developers instead of consumers.
We have a finite cleverness budget; if you spend it on your code, then your product will suffer. If you build cleverness into the platform SDKs (hello SwiftUI! hello J2EE design pattern hell!), then every product on your platform will suffer.
As for SwiftUI, I guess the only issue it the current state of its tooling, as I have heard it still is found lacking.
Yet if one listens to iOS podcasts everyone is raving about it.
Conal Elliott's work, while super interesting, does not cover the entirety of reactive programming. I found this[1] write-up to be helpful in defining what reactive programming is and it's value. The author is a member of the team working on Swift Concurrency.
And SwiftUI is compatible with anything from UIKit, so it actually doesn't hold anyone back who wants to try to do something non-trivial. If you watch the demos, particularly from the most recent WWDC, you'll see it actually makes quite complex behavior much more doable. Does that reduce the value of esoteric framework knowledge? Perhaps it does, but that means engineers now have more time to spend on the product not less.
No matter the value for money or compatibility, I will never leave macOS for windows because of it. I bought a hugely powerful windows PC in 2018 and the audio performance is awful compared to a 2011 macbook and my 2019 macbook is on another level.
Windows lack of attention to audio drivers is frankly staggering. CoreAudio 'just works'.
Maybe pay better attention to the sound hardware next time?
There’s a reason external hardware drivers are native on macOS.
Windows audio drivers are so poor that they’re bypassed completely and basically all major hardware producers offer either ASIO drivers on windows or native CoreAudio drivers.
Using the same high quality external sound card, I get better performance on macOS with latency and buffer than I do on a windows machine with significantly ‘better’ hardware.
I know full well sound hardware makes a difference, and macOS and CoreAudio interfaces with said hardware significantly better than windows. There’s a reason producers of pro audio interfaces write their drivers natively for macOS and use ASIO for windows.
I’m talking about pro audio production tools here, not playing a few mp3s.
...in your opinion. In the opinions of people who manage IT for the vast, vast majority of businesses around the world, where most of the serious software users exist, that title belongs to Microsoft Windows.
Apple is so overtly anti-consumer that I just can’t take opinions like this seriously. And Swift is a trash language with a shitty developer experience just like Obj-c was before it. They couldn’t even get strings right. Nobody wants it outside of Apple’s little bubble.
Give me a native project over Web stuff any day of the week and I up for grabs, yet I do Web related projects since 1999.
Objective-C and Cocoa is great compared with the chaos of npm, yeoman, babel, node, deno, coffeescript, Typescript, bucklescript, angular, react, ember, dojo, prototype.js, jQuery, Vue, svelte, scss, and whatever someone comes up next month to improve their CV.
I am so happy that thanks to WebAssembly I am getting Flash back.
Is async/await more suitable for user interfaces?
co-routine (Java Loom's one, Go one, Scheme one, etc) requires to be able to serialize and deserialize a part of the stack (move the stack on the the heap and vice versa), so it's hard/impossible to implement with non managed runtimes which like in C or objective C.
In C, you can declare an address/pointer to an address on stack (using &), but with a co-routine mechanism, the addresses of parts of the stack are not constant.
If you have a managed runtime, you rewrite those kind of pointers when you copy parts of the stack back and forth.
Also, here's an interesting use of async/await: software hyperthreading: https://isocpp.org/blog/2019/09/cppcon-2018-nano-coroutines-...
But won't you run into issues where you don't want to call certain code from that UI thread? Isn't that "code coloring"?
In your example, for Swing applications you should use invokeLater(Runnable r) anyway, and it doesn't matter what implements the interface.
How can you set a shader and then draw a mesh each in two virtual threads without possibly interweaving their execution, for example?
... assuming any other tasks/coroutines running on the UI thread are being cooperative and not doing bad things like waiting on synchronous functions or otherwise hogging the UI thread too much. Done right, it's a big performance and maintainability win over multithreading, but when done poorly, it can result in large variance in latency/responsiveness of any individual task.
The benefits that Go (and potentially Loom) provide are with the scheduler. When Go code calls into other Go code, it's fast because preemption points can be inserted by the compiler. The goroutine is parked when it is blocked (on I/O or some foreign function), and in the slow path this logic is executed on a new kernel thread.
Although the Go approach involves more overhead in the slow path, wrangling blocking code to work with the scheduler has cross-cutting implications for library design. I.e, I don't have to worry that a library I import that does file I/O will pin my goroutine to a blocked thread by using a blocking syscall.
It seems like it would be much harder or impossible in languages that work to hide these details. How do you go from implicit blocking to explicit native thread usage?
At least Loom seems to be keeping native threads so Java code can simply fall back to that, I guess.
This of course requires a scheduler that needs to handle hierarchical control of execution rather than having an unformed mass of tasks, but nurseries provide other benefits as well.
1. We know that no JVM frame contains a raw pointer to anywhere else in the stack.
2. We know which stack frames are JVM frames, and which are native frames.
3. We know that there are unlikely to be native frames in the portion of the stack between the point where we yield control and the point we want to yield to.
4. We can change the standard library to check if we are in our new lightweight thread or not and act appropriately.
Knowing these we can avoid function coloring and push the complex management of green threads into the standard library, and reuse existing task scheduler code to manage these virtual threads, and we can work to make thread local variables etc work seamlessly with this new concurrency abstraction.
This puts us in a very different design space. We can move a portion of the stack to the heap when a thread is unmounted, and we can change things about the GC and other internal systems to make them aware of this new thing that can be in the heap.
This type of approach would be much harder in a language like swift that tends to coexist with C and other languages that use raw pointers or a non-moving GC, so I think the question is not which is the better approach but which is the better approach within your language ecosystem.
Of course, there are technical differences that make certain design choices easier or harder in different languages. As aardvark179 mentions above, Java doesn't have pointers into the stack, and intermediate FFI frames are extremely rare (also due to the platform's established and large ecosystem).
(I'm the technical lead for OpenJDK's Project Loom)
Narking on grandpa's compiler is just what some kids do.
Now, I actually still believe Java is doing the right thing (hence the name). And the right thing if successful is more right, thus the name. And you make a good point in that Java is already popular and can probably afford to take a really long time to bring a better async story to its table.
But lately I've been wondering if Loom can fail? It seems to still somewhat be experimental, is there any chance it wouldn't ship? That after all the hard work, it doesn't get merged into OpenJDK? Maybe it turns out to be more hassle, break too much user code, be too difficult to bring back to Graal, or fail to be natively compiled by Substrate, etc.
Hopefully not, and I'm sure you're more aware then anyone of the risks here. And I trust you and the team working on Loom. But sometimes seeing what's happening and being done in C# and other languages, I do stop and wonder if worse is better, but when Loom does arrive (if ever?), I'll probably be happy that this was the route Java took.
Actors however are a static concept, we know at compile time which actor is local, and if we have the right one active. So the check about whether you’re on right actor happens at compile time.
You can think of it as if queues are part of the type system, and the compiler can work out statically what queue is used by any code, and so it can label an entire call tree’s queues by control flow analysis.
Because of this you wouldn’t dispatch onto the same queue twice and deadlock. Rather, the compiler would see that the correct actor is local already in the call tree and there’s nothing to do so the dispatch is elided. It would only “switch queues” if it needs a new one.
Besides the deadlock issue, the other advantage of this is it gets optimized out if you have several calls with the same actor.
https://blog.logrocket.com/a-practical-guide-to-async-in-rus...
> Some libraries require you to use a specific runtime because they rely on runtime internals to provide a solid API for you to use or wrap their own API around an existing runtime. One example is the actix_web web framework, which wraps its own API around tokio.
So if you want to use two libraries that follow this approach, now you have faced with the hurdle to combine two async/await runtimes into the application.
Even C++ knows better in this regard.
Actually Rust ones might even be better, since there is at least a well-defined interface that executors should follow (through the `Waker` type). The C++ executor interface discussion is probably older than the coroutines proposal, but afaik there still hasn't happened any standardization.
Everything around Send/Sync is perfectly interoperable, because it's a rule for static analysis, not a runtime.
No one takes the next steps and introduces the high-level primitives you actually need to work with actors and concurrency in a sane manner: monitors, messages, supervisor trees. Erlang has been around for thirty years, people.
[…]
Things to note about this example:
- Declaring a class to be an actor is similar to giving a class a private queue and synchronizing all access to its private state through that queue.
- Because this synchronization is now understood by the compiler, you cannot forget to use the queue to protect state: the compiler will ensure that you are running on the queue in the class's methods, and it will prevent you from accessing the state outside those methods.
- Because the compiler is responsible for doing this, it can be smarter about optimizing away synchronization, like when a method starts by calling an async function on a different actor.”
The article also links to
- https://forums.swift.org/t/concurrency-actors-actor-isolatio... (pitch for implementing actors)
- https://github.com/DougGregor/swift-evolution/blob/actors/pr..., which links to https://github.com/DougGregor/swift-evolution/blob/actors/pr... (proposal for implementing actors)
I don’t know Erlang well, but what’s missing?
I feel the Swift lang. design is like amateur hour at its best, trying to reinvent the wheel, but still end up where it started, but at worse overrall usability. Just re-arranging chairs. 8 years later, and still Objective-c + GCD combo is better at multithreading.
In comparison: Java had decent multithreading support since version 1.1,(one year later after its release) and it had NIO by JDK 1.4 and full modern multithreading by JDK 1.5.
Swift is a couple of years behind because it is trying too hard to be cool and different.
async/await, a way to model concurrency, and mutexes/semaphore/etc, a way to safely share data, belong to separate categories and one does not preclude the usage of the other, especially if your coroutines are allowed to run on different threads.
You can definitely find iOS/macOS developers who prefer objective-c, but they're in the minority. The vast majority would say that Swift is way, way more usable than Objective-C. Obj-C still has some advantages (dynamism being a big one) but for most tasks Swift makes developing both easier and more safe.
Instead, substitute "few mainstream language designers" and it stands up. By mainstream I mean Java, Javascript/Typescript, C#, C, C++, Python and such. Most have introduced async/await. None has meaningfully gone beyond that as far as I'm aware. Working with Erlang's concurrency model is a refreshingly simple, consistent mental model compared to the mismash of concurrency features provided by the mainstream. In Erlang, it's as simple as:
1. Do these things need to happen concurrently?
No: regular functions. Yes: spawn regular functions.
Compare that to the mainstream:
1. Do these things need to run concurrently?
No: regular functions. Yes: are there only a few, and/or do I need strong isolation?
Yes: use OS-level processes No: do I want the OS to take care of scheduling / preemption?
Yes: use threads No: use async/await
Is there a chance that my async operations will be scheduled across multiple OS threads?
No: get speed boost from no scheduling overhead, but remember to yield if there's any long-running actions. Yes: build my own likely-buggy, half-baked scheduler
Oh, and as a bonus: run back up the entire call stack to make all functions that call mine async.
And that's before we get to error handling. I'd take Erlang supervision trees _every day_ over trying to figure out which nested async callback function generated an exception.
One of them (Orleans) is used to power Halo's backend.
internal func refreshPlayers(firstParameter: String, secondParameter: Int, thirdParameters: Float) async { }
these small mistakes are starting to add up with Swift. They should really nip these things in the butt instead of adding upon the inconsistencies. Its better to make bold decisions now that you know is right than to change them 10 years from now when everyone is already use to them, which is what some older languages are dealing with now.
Why not just have it be async func refreshPlayers() { }
Swift already uses the space at the end of the function declaration for things like throw and generic constraints. I personally don't see an issue with where it is other than I also write a lot of JavaScript and the context switching between languages might take a couple seconds.
If there was an argument that actually made sense, I'd understand, but there is none.
this is the argument: (https://forums.swift.org/t/swift-concurrency-roadmap/41611/9)
If, rather, you're looking at the function definition, you already need to read the whole thing. You need to read what the parameters are, whether or not it throws, what its return type is, etc. So putting the effects (like throwing or async) after the name makes it IMO much more scannable. Because the first thing I want to know is the name. I only care about those implementation details once I know I'm looking at the right thing.
There's also no universal consistency between languages. Rust put `.await` after the function invocation rather than as a prefix keyword. C++ used `co_async` and `co_await` as the keywords to avoid breaking lots of people's code. Swift should put the keyword where it makes sense from a consistency standpoint with Swift, which in this case is with `throws`.
Which feels strange coming from Apple. Google showed up to use this with Go, write a tool that updates the code from version "x" to version "y" instead of being beholden to source compatibility issues in situations like this.
Essentially the tooling in the Apple ecosystem is rough to work with and feels poorly thought out - it feels like the teams work independently and then mash their stuff together right before go live.