Exponential time complexity in the Swift type checker
cocoawithlove.com
cocoawithlove.com
What's wrong with a type resolver that resolves (or not, with an error!) greedily in order of precedence?
Here's how I would solve
let a: Animal = 1 + -(2)
Ok, 2 is an int. If that gives problems down the line, too bad. -(2) is an int, if that gives problems, too bad. 1+-(2) is int+int so result should be an int. a is an Animal, try to convert int to Animal. Fail. Error!One thing in particular that scares me with the constraint approach is that it seems to me (though I may be wrong) that whether overflow occurs or not is not immediately obvious.
let a: Double = -(INT_MAX) + - (INT_MAX)
whats to say those INT_MAXes wont somehow be interpreted as Doubles and a will be -2 INT_MAX ?Swift brings together three features that can be hard on type inference:
* Overloaded literals. When overloaded literals like maps can themselves contain literals, there are actually quite a few possible overloadings to choose from.
* Subtyping. We infer not types but type bounds (ranges).
* Overloading.
Haskell has notably no overloading at all and no subtyping. Somehow Scala manages to combine all these things and a quite astonishing conversions system, too.
First answer: since Swift has no implicit conversions, greedily selecting types would create a situation where mathematical expressions or even function parameters that use Float instead of Double would be completely impractical. Every floating point literal would be interpreted as Double and compiler error on attempt to use as a Float. You'd need to manually construct a Float around every literal to actually get Float. The same problem would plague and UInt, Int8, etc – any value would require manual construction from a literal of type Int.
Second answer: Since Swift has no implicit conversions:
let a: Double = -(Int.max) + -(Int.max)
would always be a compile error because the right-hand side is an Int and can't be converted to Double. This isn't a type inference or type checking problem, it's a "no implicit conversions allowed" error. You would need to write: let a = Double(-(Int.max) &+ -(Int.max))
for the line to compile (I've used the overflow allowed addition operator '&+' since the regular '+' will trigger an abort if it actually overflows).However, in order to perform the simplistic algorithm that the parent comment imagines, we have to give up overloading. If you have f(A) and f(B) and an implicit conversion between A and B, and we type f(x) (where x is an A), both f(A) and f(B) could be selected. Since they might have different types, a larger expression it is a part of could be valid by picking one and not the other, but we cannot know which until we go through all possibilities. That's an exponential algorithm too.
It won't apply more then one level of conversion, though you can perform recursive operations in the type system. Shapeless is a library that uses this for ad-hoc typesafe programming without boilerplate.
let a: Double = -(9223372036854775807) + - (9223372036854775807) fun dist ((x0, y0), (x1, y1)) = let
val dx = x1 - x0
val dy = y1 - y0
in
Math.sqrt (dx * dx + dy * dy)
end
Because of that, it also has its exponential time complexity (see for example http://spacemanaki.com/blog/2014/08/04/Just-LOOK-at-the-humo...)What makes things especially bad for Swift seems to be that it has function overloading and uses lots of it in its standard library, as that increases the constants in the exponentiation (for example, with 6 choices to make and 10 vs 2 possible overloads per decision point you get 10^6 = a million versus 2^6 = 64 options to consider)
That doesn't mean there isn't a good reason that Swift's inference is exponential – I just think HM's exponential time behavior is a red herring.
Swift's type checker does also have an exponential path for protocol constrained generic parameters which is functionally equivalent to the exponential path for classical Hindley-Milner. However, there are "pseudo" linear optimizations for this too that Swift should also consider.
If your compiler needs a constraints solver to determine the type of an expression, it belongs in a science exhibit, not into a real-world toolchain.
Don't try to be clever! Give me a compiler that's predictable, reliable and fast. If that means I have to add some type information here and there, that's just fine!
The following line will give an error if you try to compile it in Swift 3:
let a: Double = -(1 + 2) + -(3 + 4) + 5
Reminds me when Golang said you should avoid using numbers greater than about 20k in your program to avoid GC problems. I.e. farcical.What would be hilarious is if the swift dev team releases a compile profiler that shows "hot lines" that take a long time to compile, while not solving the actual issue.
Isn't this unavoidable with conservative garbage collectors? The GC looks at your fields and believes your integer is a pointer… It cannot know better.
Swift doesn't have that issue, but reference counting has its own risks of memory leaks through mishandled weak references, and a performance overhead.
Does ARC have an overhead relative to GC? If if it does, it at least prevents latency spikes.
It doesn't prevent latency spikes. As an extreme example, create a linked list or tree with a billion nodes, and let its root go out of scope. That will call free a billion times.
With GC, you can see a latency spike when every request creates such a list and they aren't freed until, due to memory pressure, many are freed all at once. Some of the requests will get stuck waiting for a linked list node and have very high latency relative to the other ones.
The strength of ARC for interactive applications is its consistency.
I agree that the statement about avoiding large numbers was farcical, but it was not made by "Golang" but rather by someone on the go-nuts mailing list. Your sentence makes it sound like this was general advice for people programming in go from the go team, but to the best of my knowledge this is not true.
[1]: https://spin.atomicobject.com/2016/04/26/swift-long-compile-...
[2]: https://github.com/apple/swift/commit/2cdd7d64e1e2add7bcfd54...
Everything makes much more sense now.