Replacing the Type Checker

Honestly, many other languages function perfectly well without full bidirectional type inference – I wouldn’t call it "handicapping". In fact, looking at Kotlin, it seems that fast and predictable type checking even unlocks something new: a different kind of expressiveness; a sense of unrestricted functional programming, where you’re not forced to break apart every other collection operation, and entire functions can often be written as single, concise expressions – something unimaginable with the current swift.

I’m also skeptical about the feasibility of significantly improving type checker errors, as many have requested. The current implementation often struggles to even arrive to a conclusion fast enough that there's an error at all.

1 Like

I made a result builder for building UIView hierarchies. I don't remember encountering any timeout with it despite writing very large hierarchies as a single expression. Seems to me the compiler has an easy time dealing with result builders when everything is a UIView.

1 Like

I would say this is a fun combination of how SwiftUI creates complicated types that are largely hidden to the programmer. SwiftUI uses a lot of generics, some, any types. Whereas any result builder that creates UIViews would be dealing with just plain-ol classes with little to no generics and hidden types, the type checker preforms well.

SwiftUI pushes the type checker to its boundaries because its use of complicated types.

SwiftUI is being used as intended, it engages with features that the language provides.

Even though I despise SwiftUI and still avoid it, I can't come up with a reason that SwiftUI would be the problem here.

I also can't see why ResultBuilder would be implicated. It is also being used as designed.

This is largely an issue of SwiftUI using Swift as designed (generics in generics in generics with lots of type constraints) and the Type Checker not being able to handle it.

I am not sure that the type checker will ever be able to solve all valid cases in a reasonable time. So coming up with a guide on how to avoid hitting the boundaries of the type checker's capabilities would be nice -- until the type checker can be ameliorated.

I also think that programmers would do well to consider the constraints of the type checker as it stands today. There seems to be this mantra "well it should work so I am going to keep it they can just fix the type checker later" at play here -- which is bad for users of API. SwiftUI does offend in this regard.

Just some thoguhts.

1 Like

Please don't interpret this as any kind of official advice because it is a very blunt hammer, and it doesn't apply to the case where your complex expression is composed entirely of calls to standard library functions.

But, if you find the type checker runs into trouble with your own APIs, the highest-impact thing you can do, by far, is to minimize overloading on names, including operators, as much as possible. It is a fact that overloading is "harder" than protocols, generics, deeply nested types, or almost everything else that one might intuitively guess contributes to slow type checking. This "hardness" extends to the difficulty of generating diagnostics -- most of the problematic cases involve overloading where neither choice leads to a clearly better outcome, both are just invalid in different ways, and the type checker has to make a judgement call about what to diagnose.

For example, say you have a bunch of commonly-used “currency types”, and each one has several overloaded inits, that all share the same argument label and thus have the same name, that take an Int or a String or any other number of types:

struct S1 {
  init(_: Int) {}
  init(_: String) {}
  ...
}

struct S2 { ... more inits ... }

And then you have an expression like:

foo(bar(S1(...), ...), S2(...), baz(S3(...)), ...)

Now, it is important that not every such expression will take exponential time to type check, far from it---but maybe foo() and bar() have complex overload sets as well, literals are involved, and some other part of the expression is complex in some way or even invalid. In this scenario you're far more likely to encounter exponential behavior.

Making judicious use of argument labels, or even replacing multiple concrete overloads with a single generic function that extracts the common behavior into a protocol somehow, can help.

This obviously isn't possible with operators, but as an API designer, it is also worth considering if a descriptive method name might even be better in some cases than overloading some operator.

Sometimes we see reports of slow type checking related to operators where, eg, the project introduces a public overload of func +(_: CGRect, _: CGPoint) or something like that, with two "unrelated" concrete types. I certainly cannot fault anyone for solving a problem in front of them using the tools provided by the language --- but it is worth keeping in mind that at least with the current design of operators, this sort of thing would be extremely difficult to make fast, and such overloads can even slow down unrelated expressions that happen to involve those operators.

(Something we've discussed in the past is possibly changing the design of operator overloading to fix some of these non-local performance effects, but I believe any such change would go in the direction of completely banning such "bespoke" overloads among unrelated types, rather than making them fast.)

15 Likes

I would love to test a special mode that disables all bi-directional heroics. I guess I'll have to use var v: Float = Float(1.0) and ExplicitType("literal") + ExplicitType("literal") everywhere so it's a tradeoff, but on the bright side would be lighting fast to compile!

2 Likes

Something we've discussed in the past is possibly changing the design of operator overloading to fix some of these non-local performance effects, but I believe any such change would go in the direction of completely banning such "bespoke" overloads among unrelated types, rather than making them fast.)

Sounds good! Please let the door hit overloads on their way out. Violently and repeatedly.

Thanks for these tips, Slava.

It seems the OP's complaint is aimed mostly at the "can't type check in reasonable time" message and these seem more commonly a problem in code using result builders. If it is even possible, how can we apply this advice to code like this?