Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Lightweight API Design in Swift (swiftbysundell.com)
109 points by ingve on Nov 26, 2019 | hide | past | favorite | 56 comments


> Since each transform needs to perform vastly different image operations, using an enum in this case would’ve forced us to write one massive switch statement to handle each and every one of those operations — which would most likely become somewhat of a nightmare to maintain.

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.

[1] https://www.infoq.com/presentations/Simple-Made-Easy/


Agreed on all points. One of the main metrics I use to assess maintainability of code is 'how many places do I need to edit to make a change?' (within the same file or worse, in other files too), 'how easy is it to find those places?' and 'how easy is it to make a change in one of those places but overlook another needed one?' On pretty much all of those counts, a single switch statement will tend to beat an inheritance hierarchy.


What you describe is called the Expression Problem [1] in programming language design and there is no simple formulaic answer on which method is better. I think you have to consider many aspects of your code's current design and possible future evolution when deciding which approach to use. For example: do you expect to have more types of transforms, or more operations/method per type of transform? It also means you can't nitpick a limited tutorial for focusing on one approach vs. the other.

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.

[1] https://en.wikipedia.org/wiki/Expression_problem


Java and C++ have no issues representing has-a relationships.

The issue is developers not learning how to use their tools.


This post does a good job of highlighting one of my favorite things about Swift: it's almost always possible to express things exactly how you want. And Swift is getting even better at this with features like property wrappers.

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.


I love Swift as well and prefer to use it for everything wherever I can, but you may find yourself running into walls if you try to get too smart with it. Though I suppose that’s inevitable in any language.

An example of something I’ve been struggling against:

https://forums.swift.org/t/swiftui-extension-for-os-specific...


Making DSLs in Swift is surprisingly easy, as this shows, but a brief word of warning: if you overdo it, your API, while pretty at the use-site, can end up being hard to actually use–you'll create what's essentially a read-only language. SwiftUI has taken this style of design to about as far as it's possible to go, and while it's pleasant to read it has a number of pains with regards to API discoverability. Plus the compiler absolutely hates everything about it :(


I have the feeling that SwiftUI was slightly rushed. Swift's language development has been slow and methodical, with a lot of care put into making sure new language features are not only substantially valuable and well conceived from a design standpoint, but also that they don't risk moving the language in a direction which would limit potentially better possibilities in the future. This can be frustrating, as it means it takes a long time for obvious and useful features to make their way into the language (looking at you variadic generics), but the tradeoff is that Swift is a very elegant and well-designed language which doesn't have some of the awkward edges you see in other languages which have to exist for legacy reasons, like you see in C++.

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.


It may also have been rushed due to the ABI stability deadline. I'm speculating, but it is easy to imagine that the SwiftUI team saw the window for getting new language-level features closing.

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.


> is easy to imagine that the SwiftUI team saw the window for getting new language-level features closing.

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.


I think the yearly release schedule is the big issue. SwiftUI was pushed out a bit too early IMO, but it wasn't a year early so I totally get why they released it this year.


Off topic, but I've been following Sundell for some time now from his podcast and think he's doing some really cool things in the Swift space.


Discovered him from his podcast as well, it's wonderful. Here's an example interview with Chris Lattner: https://www.swiftbysundell.com/podcast/50/.


Thanks for this! I didn't know they did one with Lattner, should be awesome.


Probably one of the most elegant languages around.


Yes, it’s my favorite language.

Now it needs better cross platform support to be more widely accepted.


And a proper IDE and a faster compiler.

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".


Absolutely. One of the things I hate most is this thinking "run-time is more important than compile-time".

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!


> "run-time is more important than compile-time"

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.


Sometimes you introduce certain restrictions in the language just for the sake of simplifying the compiler. This is allowed, valid and will be understood by the developers.

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.


Those kinds of restrictions can have their own productivity pitfalls. For instance, it might be the case that requiring declaration prior to usage explicitly will speed up the compiler, but it might also mean that you can't move blocks of code around without paying careful attention to the order of declaration. I think having a permissive compiler, but also being able to profile the compilation process is a reasonable compromise.


Possibly, but there are other kinds of compromises such as allowing arbitrary order only within a class, the C++ way. Or another one: the first (quick) pass treats all function and reference type definitions as forward declarations and skips the details. This way the compiler might be able to do more permissive compilation though not 100% accurate like Swift currently does.


But run-time is more important, isn't that why we compile stuff with optimizations that take ages to process when building products for the end users?

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.


My half joke is the optimizations slow down compilers. So people then try to make the compilers faster by increasing the levels of optimization. Which means they have to work harder. So they are slower.


I guess ideally this should be a tradeoff in the hands of the developer right? I should be able to produce debug builds quickly so that I can iterate quickly, and if I want a super-optimal release build it's fine if it takes longer.


In swift things have a module-wide visibility by default, that's why it often rebuilds the entire module after a single change that could implicitly affect other files. The only viable solution at the moment is to break the project into as many small modules as you can.


It's also useful to profile your compile times. Sometimes the compiler is spending most of its time on some small sections of code.

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.


Swift also provides a compiler option specifically for debugging long compilation times: "-Xfrontend -warn-long-function-bodies=100"


Ah, Cartography. Works great in a small project. We ended up moving to standard anchors for almost everything, with some helper methods to handle things like pinning to superviews.


I hear server-side swift is gaining traction


It is very easy to learn and use. Well compared with java and objective c. Learn it and push the app with v 1.0 is a bit hard but it works. In fact the app still there. Great language.


I'ts def. one of the best designed languages, and has a ton of progress. They're also very agresive with deprecating things that should be changed.


I couldn't agree more. I'm anxious to see it build up a rich developer ecosystem like Java has. The availability of so many 3rd party libs for Java is a huge advantage. If Swift gets to that point I can see it taking over all of my server-side development.


Which types of libraries in particular do you feel are missing?


Words I never thought I'd see attributed to Swift, it's a blend of a Scala like design with Objective-C parts.


It takes many of the good parts of both and wraps it in some very nice syntactic sugar. What's not to like?


Credit where credit is due, they did unicode "right", if you need to deal with those shenanigans it's really nice.

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.


F# and Kotlin seem better to me, and don’t have that hideous syntax for the closures. But it’s for sure another world compared to the awful objective c.


I wouldn't say Kotlin is significantly better than Swift in terms of syntax, they are largely similar to the point you sometimes think: someone copied this from the other, I wonder who was the first.

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.


Swift's ARC implementation has a little bit of performance catchup to do in regards to tracing GCs.

https://github.com/emmericp/ixy


IMO this example should be taken with a grain of salt, since at a cursory glance it looks like they are leaning heavily on reference-types, which is not really idiomatic swift, and is a worst-case for tightly-looping code which this is. I would love to spend even a half hour trying to optimize this code to see what the results could be, but there is specialized hardware required to run the benchmark, so it's not really practical for 3rd parties to attempt to achieve a better result.

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.


Languages with tracing GC implementations also have value types, thus they would keep winning.

Top performance reference counting implementations are tracing GC in disguise.



Kotlinc takes 10 seconds to compile Hello World, 30 seconds with wasm target. Seems almost conceptually unfixable.


swiftc isn't also not that blazing fast.

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.


I would say Rust is a better point of comparison for Swift given the emphasis both languages have on strong type systems and static analysis. Neither one is super fast, but 10 seconds for a hello world is insanely slow.


And both are slowed down by LLVM.

F#, Standard ML, OCaml and Haskell also have such strong type systems.


Swift was out before kotlin.


Dunno about F# as I’ve never written it, but Kotlin, while not a bad language, to me mostly feels like Swift with extra syntactic friction points. Among other things, its property getter/setter syntax feels awkward compared to those Swift.


What’s hideous about Swift closures (though I guess everything has always room for improvement) and how are F# and Kotlin better?


(s1: String, s2: String) -> Bool in return s1 > s2 } In nested closures you can’t use the $0 syntax, swift type inference is pretty poor compared to F# and so a lot of time I have to explicitly specify the types and the in is much less clear than => or ->. To make it even more frustrating I have to specify the @escaping attribute in a lot of methods that accept closures as an argument.


yes, because of memory management. It's a good thing.


This puzzled me too. Ok, blocks in Obj-C do look, err… interesting, but closures in Swift are totaly fine.


Maybe the use of "in"?


> the awful objective c

:(


Reminds me of this API design post for Go: https://dave.cheney.net/2014/10/17/functional-options-for-fr...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: