I see that there are a number of fine answers already. Some of which I will be repeating but sometimes you just want to put things in your own words. I will comment with some examples from C++ because that is the language with which I have the most familiarity.
What is necessary is never unwise. Type inference is necessary to make other language features practical. In C++ it is possible to have unutterable types.
struct {
double x, y;
} p0 = { 0.0, 0.0 };
// there is no name for the type of p0
auto p1 = p0;
C++11 added lambdas which are also unutterable.
auto sq = [](int x) {
return x * x;
};
// there is no name for the type of sq
Type inference also underpins templates.
template <class x_t>
auto sq(x_t const& x)
{
return x * x;
}
// x_t is not known until it is inferred from an expression
sq(2); // x_t is int
sq(2.0); // x_t is double
But your questions were "why would I, the programmer, want to infer the type of my variables when I read the code? Isn't it more faster for anyone just to read the type than to think what type is there?"
Type inference removes redundancy. When it comes to reading code it may sometimes be faster and easier to to have redundant information in the code but redundancy can overshadow the useful information. For example:
std::vector<int> v;
std::vector<int>::iterator i = v.begin();
It does not take much familiarity with the standard library for a C++ programmer to identify that i is an iterator from i = v.begin()
so the explicit type declaration is of limited value. By its presence it obscures details that are more important (such as that i
points to the beginning of the vector). The fine answer by @amon provides an even better example of verbosity overshadowing important details. In contrast using type inference gives greater prominence to the important details.
std::vector<int> v;
auto i = v.begin();
While reading code is important it is not sufficient, at some point you will have to stop reading and start writing new code. Redundancy in code makes modifying code slower and harder. For example, say I have the following fragment of code:
std::vector<int> v;
std::vector<int>::iterator i = v.begin();
In the case that I need to change the value type of the vector to double changing the code to:
std::vector<double> v;
std::vector<double>::iterator i = v.begin();
In this case I have to modify the code in two places. Contrast with type inference where the original code is:
std::vector<int> v;
auto i = v.begin();
And the modified code:
std::vector<double> v;
auto i = v.begin();
Note that I now only have to change one line of code. Extrapolate this to a large program and type inference can propagate changes to types much more quickly than you can with an editor.
Redundancy in code creates the possibility of bugs. Any time your code is dependent on two pieces of information being kept equivalent there is a possibility of mistake. For example, there is an inconsistency between the two types in this statement which is probably not intended:
int pi = 3.14159;
Redundancy makes intention harder to discern. In some cases type inference can be easier to read and understand because it is simpler than explicit type specification. Consider the fragment of code:
int y = sq(x);
In the case that sq(x)
returns an int
, it is not obvious whether y
is an int
because it is the return type of sq(x)
or because it suits the statements that use y
. If I change other code such that sq(x)
no longer returns int
, it is uncertain from that line alone whether the type of y
should be updated. Contrast with the same code but using type inference:
auto y = sq(x);
In this the intent is clear, y
must be the same type as returned by sq(x)
. When the code changes the return type of sq(x)
, the type of y
changes to match automatically.
In C++ there is a second reason why the above example is simpler with type inference, type inference can not introduce implicit type conversion. If the return type of sq(x)
is not int
, the compiler with silently insert an implicit conversion to int
. If the return type of sq(x)
is a type complex type which defines operator int()
, this hidden function call may be arbitrarily complex.