Why type-inference?


(Ehud Adler) #1

Type-inference is a core feature in the Swift language. A bunch of previous discussions revolve around it and it's a pretty visually apparent part of the language.

There are a few (many?) articles written on the fact that when dealing with large projects, type-inference can make for much slower compilation times. (One of those articles)

I heard that companies like Uber switched to declaring types let foo:Int because of this reason (I do recognize I only heard this and don't know it as fact, though it is believable)

In terms of readability, I feel it's subjective, though I feel Int foo is more readable than let foo:Int.
In terms of consistency/clean code, I agree all declarations beginning in let is better than Int, Double...etc

That being said, I'm curious why the core team (maybe @Chris_Lattner3) decided to implement type-inference. What value did they/the community see in it over declaring the type?


(Erik Little) #2

I can't speak to the original authors, but as a user of many "modern" languages, the biggest win for me is reduction of tedious typing and what I think is noise.

If this is true, it's a pretty poor decision IMO. From my experience, early Swift was indeed pretty slow at deducing types in complex projects, but newer versions have improved it greatly, and it should only improve going forward.


(Jon Shier) #3

Uber is indeed an extreme case. VIPER + Swift 3 was not a good time, so they resorted to all sorts of things to get build speeds up, including explicit types. In most apps, the occasional check of build times to ensure nothing is taking too much time, and perhaps adding an annotation only there, would suffice.


#4

I wonder if compiler can add that kind of suggestion if inference takes longer than some threshold, to make subsequent compilation better (or compilers already cache result from the last run, I don’t know).


(Ehud Adler) #5

@Jon_Shier @nuclearace So even in large projects there won’t be visible compilation slowdown assuming you annotate type in specific places?

I guess what I’m asking is, are you saying that there is really no (maybe very slight) downside to type-inference in terms of compilation time?


(Jacob Williams) #6

The compiler has a timeout where it will stop trying to guess the type of your statement. I don't think it would be a good idea to generate warnings just because the compilation of a particular statement is taking too long.

If you really want to improve your build times I would suggest using a tool like the Xcode Build Time Analyzer. It can give you a break down of how long each file/line takes to compile. I've used this in certain of my own projects to get build times down from 30 seconds to 3 by only adding a couple of explicit annotations or by splitting a particularly complex statement into multiple smaller statements.


(Erik Little) #7

I would say that the benefits of type inference far outweigh the overhead it introduces. It's a pretty key feature of Swift, and not using it is throwing away one of the nicer things of Swift.


(David Sweeris) #8

Yeah, with one exception, I generally only use type annotations where the compiler makes me. For what I can only describe as "my own internal style guide", I'll frequently annotate the properties of a type once I'm happy with its design (but that's just me being weird).


(Slava Pestov) #9

I think you're talking about one particular form of type inference -- allowing the type to be omitted on a stored property declaration. I think there is a debate to be had whether this is the right tradeoff between readability and concision. I tend to think omitting the type on a local variable is helpful, on stored properties of types, I'm not so sure. However at this stage of the language's evolution, a source breaking change like banning inferred stored property types would be quite a drastic change, and I'm not sure it will ever meet the high bar we've set for these changes.

However that's not the only place where the compiler infers types. There are also the types of intermediate values in expressions -- and this is a form of type inference that even C has:

foo(bar(y + z) + x * 3)

It would be quite inconvenient if you had to declare the type of x * 3, y + z, bar(y + z) + x * 3, and so on. So I think you'll agree that this form of type inference is helpful.

Another form of type inference is inference of generic parameters when calling a function or constructing a type:

func doSomething<T : Sequence>(_ t: T) {
  ...
}

doSomething([1, 2, 3, 4, 5])

We don't even have a syntax to explicitly pass generic parameters to a function call, but if we did, you'd have to write something like doSomething<Array<Int>>([1, 2, 3, 4, 5]).

Type checker performance is a separate issue. Adding more type annotations will help the type checker in some cases, but its not a panacea because even in the presence of annotations the type checker still has to verify that the annotation was correct, infer types for intermediate results, figure out what overloads are selected, and so on. The performance story is more complicated than "more annotations = fast".

I hope in the future we can spend more time on making the type checker fast. I believe most of the performance problems users face today can be solved without imposing a specific coding style on the programmer. Of course, if you choose to use more or less annotations in different places, that is your decision and it should be driven by understandability of the code and not compiler implementation concerns.


(Jacob Williams) #10

I would add to this that after using the Build Time Analyzer, you will see that the overwhelming majority of type inference overhead is negligible. As others have pointed out, type inference has drastically improved over first few versions of swift.


(Ehud Adler) #11

Thanks for the great answer!
I was not looking to make a change to the language as much as just understand the thought process that went into a really key part of the language. Just for my own understanding.

I never really thought about the foo(bar(.. example you gave but that makes a lot of sense.

Now I am interested in seeing how the Type Checker works in Swift (maybe even help contribute to it in the future!).


(Slava Pestov) #12

It's also worth pointing out that expression type-checking is only one slice of the pie, so to speak. Other contributing factors to compilation time are the declaration checker (this includes name lookup while checking expressions), the Clang importer, deserialization, SIL generation, SIL optimization, LLVM optimizations... there's a lot going on and I think in most programs expression type checking is not the dominating factor.


(Daniel Berger) #13

I believe the reason given in a WWDC video for this feature is that Swift is a modern language and type inference is an expectation of a modern language.


(Ehud Adler) #14

Interesting. I’m honestly not a fan of the reason “because other modern languages have it” but :man_shrugging:


(Jean-Daniel) #15

Other modern languages have it because it is a helpful feature. It allows to avoid typing redundant information everywhere, even when the compiler don't need them, making code simpler and easier to change.

This is especially useful when working with complex types (like lazy collection, generics, …).

And as swift does not prevent you to use explicit typing, you are free to do it as well if you want.


(Pierpaolo Frasa) #16

In Java, you used to have to type stuff like

ArrayList<String> list = new ArrayList<String>();

or even

TreeMap<String, ArrayList<String>> map = new TreeMap<String, ArrayList<String>>();

all over the place.* Even Java realised how insane this was and finally allowed some limited type inteference where you could use

ArrayList<String> list = new ArrayList<>()

I don't have any hard evidence, but I assume that this was one of the reasons why many people left the world of compiled languages and ventured into Perl, PHP, Python, Ruby, etc. To get people excited about static typing again, type inference (at least to some extent) seems to me like a necessity.


(*) Yes, I know that this wouldn't be good Java style because you're supposed to code to interfaces (List, Map), but the point still stands.


(Jean-Daniel) #17

In Java 11, this can even be:

var list = new ArrayList<>()