Lightweight API Design in Swift
swiftbysundell.com
swiftbysundell.com
I don't mean to pick on the author, but I've seen this line of reasoning a few times before. It's the same argument that has been used in the past to justify inheritance hierarchies in OOP languages. I used to believe it too. However, I don't think this is actually true. In fact, I'd argue the opposite: switch statements, if used well, are _extremely maintainable_. Even though a switch statement might handle many cases, it does not become more complex [1] by doing so. If we're concerned about the length of the individual cases, we can easily replace each one with a function call. Fundamentally, in the example from the article, we'd like to map a piece of data (the operation with its parameters) to some behavior (actually performing the operation). A switch statement is one of the simplest ways to do that.
Fortunately swift (as well as Rust or Kotlin) has excellent modern toolbox that includes protocol conformance and algebraic data types so you can use either one.
Keep in mind that swift protocols avoid many of the pitfalls of Java\C++ school of OOP design you might have seen before that can only express "is-a" relationships.
The issue is developers not learning how to use their tools.
Swift has a few hurdles to overcome, like better cross-platform support, solutions to the performance cliffs related to ARC, and more parsimonious copy-handling of value types, but I think it has the potential to be one of the most productive and powerful languages for building software in the next few decades.
An example of something I’ve been struggling against:
https://forums.swift.org/t/swiftui-extension-for-os-specific...
In that respect, it seems like function builders skipped that whole process. It's not that they're a bad feature, and similar facilities exist in other languages, but the fact that they were added to the language without a lengthy vetting process seems very out of character.
In a way I don't even know if it's really needed. I have a project which uses declarative code to achieve similar goals to SwiftUI, and you can get really close without function builders. For instance, you can write code like this:
var view: [View] {[
Foo(...),
Bar(...),
Baz(...),
]}
It's not quite as clean as the function builder approach, and it doesn't have all the same benefits (i.e. producing a typed result vs an array of protocol instances) but it's very easy to understand and doesn't require any additional language features.My theory is that since Google has been pushing Flutter aggressively, Apple needed to answer with a "modern" FRP-style workflow for iOS to retain its developer mindshare.
More generally, Apple's yearly splash model seems to dictate that teams either release or wait 12 months, if waiting is even an option at all. It makes me wonder how many Apple APIs would have been revised under a more flexible release model.
Would function builders have much impact on ABI stability? My assumption was that they are essentially just a front-end transform: i.e. the SIL generated from a function builder would be equivalent to writing it out "long hand" - the same way that Codables can essentially be thought of as the compiler writing out boilerplate Swift code for you.
Now it needs better cross platform support to be more widely accepted.
Xcode is dreadful with its strange fetish for recompilation even if there were no changes and the compiler is slower with every iteration. The solution put forward for this always seems to be "buy newer hardware".
Well the compiler and the IDE are runtimes too, we developers are users too, we sometimes work on our laptops unplugged and we do want our software to be fast and efficient!
This is a pitfall of Rust as well. I suppose there will always be a tradeoff between stronger static analysis and compilation time, and I'm firmly in the camp that I want my compiler to catch more issues so I don't have unexpected runtime failures, but iteration time matters too, and I'm hopeful improvements can be made in this area.
For example, for decades most static languages required a symbol to be defined before its first usage. Removing this restiction seemed like a nice little cute convenience but the outcome of it is this horrific slowdown of the build process. Personally I would agree to the restriction if I knew the compiler could be 5x faster which is pretty realistic to achieve, it seems.
Compilers are hard, compiler bugs can have a devastating effect on one's software, so it's understandable that developers may prefer to take things slow but safe.
For instance, I was working on a project where the use of one library constituted 80% of our compile time! The library allows you to declare autolayout constraints using operator overloads (i.e. `foo.top == container.top - 20`) and apparently the amount of type inference was just murdering our compile times. Changing libraries took this project from being awful to work on to quite pleasant.
:(
To me personally though the showstopper for Kotlin is that it runs on Java VM. That was the design goal of course, i.e. to become a major "upgrade" of Java esp. on Android. Java VM (with its GC) is a creation of the pre-mobile era when all we had was desktop computers, ever growing processing power and an assumption that you have infinite supply of electric power. It's a different philosophy and design. I think Google's choice of using it on a mobile platform was unfortunate, though at the time (i.e. before the iPhone) many popular mobile platforms did run on Java VM so it seemed like a natural choice.
For me Delphi, .NET Native, D are some of the targets to beat in compilation time.
They all share a common trait though, not built on top of LLVM.
F#, Standard ML, OCaml and Haskell also have such strong type systems.
It's fair to say that ARC has performance issues, but the story is a bit more complex than that. By relying heavily on value types, it's possible to write Swift code which carries very little ARC cost (although there are additional issues with copy-on-write performance). Also ARC is more memory efficient than most GC solutions, so there are tradeoffs involved.
Top performance reference counting implementations are tracing GC in disguise.
However it also adopts a bunch of the bad parts like having variance but not really having a way to handle it. Last I remember the Equatable instance for tuples was just magic, it was impossible to rely on it with the types. Even maybe 6 months ago when I last tried this if you write some code that appends maybe 6/7 arrays together immutably the compiler would go into an exponential spin.
The project I'm working on ending up dumping it entirely and moving over to being a web project written in TypeScript because it was so bad. Part of that being a laundry list of problems with the surrounding tooling like SourceKit which may or may not still apply.