Why Swift's type checker is so slow
danielchasehooper.com
danielchasehooper.com
I feel like B) is very well known in the programming language community. I certainly heard people talking about in at least ~20 years ago. A) should be obvious in the presence of overloading. I guess the type inference algorithm becomes part of the assumed semantics of the language and it's difficult to change after the fact.
1. Let's create a super advanced type inference system so that you can write Haskell like a dynamic language but have it be static.
2. Oops, now when you make a mistake, it propagates through your program and causes a type error somewhere completely different.
3. New code style rule: you may omit type signatures on local functions, but always place them on top-level functions so that they serve as a barrier to incorrect type inferences.
Trying to write untyped dynamic code in a statically typed language thus just results in frustration, those languages were made to work statically and to throw a lot of type errors, they are horrible as dynamic languages.
This isn't true at all. I write a lot of python and I don't think I've ever had a duck-typing happy coincidence that wasn't an error. No it's exactly that I'm only depending on the aspect of the type that I need at the call/use site (so I can update types while maintaining those contracts and I don't need to update any signatures anywhere).
let result: Double = -(1 + 1) + -(1 + 1) - 1
Swift 6 takes 6.2 seconds to compile this one line.That's crazy...
Swift programmers: do you face this kind of issues often?
OCaml does have +. for floats to make type checking easier, but Standard ML does not. Also, if Swift has operator overloading, why isn't it instantly clear that the RHS is an int, which can be assigned to a double?
This just looks like a performance bug in the type checker, and nothing that is inherent to HM.
Am I seeing this correctly? Is HM + polymorphic literals + implicit type conversion the cause of Swift’s exploding compile time in such cases?
In C++ terms, if you have int& operator+(const int&x, const int& y) then (1 + 1) is not ambiguous and can be selected fast. Same for unary minus etc.
The Swift devs should then blog about this example and explain step by step what is going on. If the literal "1" can be both an int and a float, that of course would be insane. Is that what you meant by "polymorphic literals"?
let result:Double = -(1 + 1) + -(1 + 1) - 1 // 10.829s
let result:Double = -(1 + 1) + -(1 + 1) // 1.724s
let result:Double = (1 + 1) + (1 + 1) - 1 // 1.721s
let result:Double = -(1 + 1) - 1 // 0.763s
let result:Int = -(1 + 1) + -(1 + 1) - 1 // 0.571s
let result:Double = -(1 + 1) + -(1 + 1) + 1
//error: the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressionsThen there's this fucker for whenever you make a programming error involving types:
>The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
Basically, your code has a type error somewhere in it, and you need to figure out where and how it's wrong.
Xcode is still awful too.
So every edit you need to take a minute to see how it renders?
In my personal experience, no. That doesn't mean it's not a weakness of the language, but in practice you very rarely write a real expression with more than one or two type inferences in it. And when you do, you can just add explicit typing.
let x: Double = 2
let y = 5 / x
y is a Double with a value of 2.5. let x: Int = 2
let y = 5 / x
y is an Int with a value of 2. let x: Int = 2
let y: Double = 5 / x
Code doesn't compile.Thankfully Apple is always open to own their mistakes and correct them.
/s
let horizpadding = self.collectionView.frame.width - ((5*50)+(4*25))
Expression took 1299ms to type-check (limit: 500ms)
Now obviously this expression could have been simplified long ago as a single constant, but the values themselves were written to help others understand what that constant meant (5 elements that are 50px wide) + (4 gutters that are 25px wide)To the Swift developers: just add source positions to your type AST, in addition to the term AST, then you'll know where a type has come from. It lets you give error messages like: expected type A (line X) but got type B (line Y).
Hindley-Milner type checkers perform well in Haskell and OCaml. I don't think this type system can be blamed entirely for Swifts problems.
Dart's static type system has been designed to avoid such problems.
Not once have I had, or heard of, a performance issue related to the Dart type checker. That's also one of the reasons why hot reload always works as expected.
I just tried the compiler flags, but the slowest expression is only 3 ms in my 1014loc project. Still very helpful to see where my slowest expressions are. I think I will set the threshold to 1ms and avoid slow expressions at all.
Doesn't that mean that your program can compile fine in the morning, and fail to compile in the afternoon because you have an HD youtube video playing in the background ?
Or the same program could compile fine on your computer, and fail to compile on less powerful CI servers ?
The dumbest possible type inference algorithm...
let x = value; // typeof(x) is now typeof(value)
... covers 80% of the cases where you'd like type inference. I'd even argue it's way more than 80%.Surprisingly, this is what C++ actually went for, and `auto` works just fine. Same in TypeScript - make a best effort guess from the value, fallback to `any` no value or type is given.
Having to write out a type every now and then costs me way less time than waiting for the compiler to finish playing minimax against itself in the latent space of all possible programs or whatever.
Which really makes me question if language design is going in the correct direction. Are Swift programs less likely to contain bugs or are they easier to debug? I know that many of Swift's creators are incredibly experienced programming languages experts, so how do they justify this insane compilation tax?
Swift compiler time is spend mostly on LLVM stuff and it isn't much slower than other modern LLVM languages (Rust/Julia etc.) <ofc. this don't count this very specific complex type checks that are super slow>
Edit: random forums on the interwebs do seem to suggest it is LLVM backed.