In an expression, each token (constant or variable) has some value. The language rules specify how to apply each operator to the values, and what the result is. If we evaluate:
x + y | Add 10 and 21 to make 31. |
x + y - 1 | Subtract 1 from 31 to make 30. |
2.5*(x + y - 1) | Convert 30 to 30.0 and multiply to get 75.0. |
2.5*(y + 1) - d | Subtract 3.7 from 75.0 giving 71.3. |
Just as the language has rules to operate on values, so with types:
x + y | The sum of integers is an integer. |
x + y - 1 | The difference of integers is an integer. |
2*(x + y - 1) | The product of a float and an integer is a float. |
2*(y + 1) - d | The difference of floats is a float. |
While values must be computed at run time, in a statically-typed language, types can be determined at compile time. This simplifies computation of values at run time, since the types are already known. In a dynamically-typed language, the full translation of x + y might look something like:
This is also a reason why statically-typed languages usually have homogeneous arrays. If array a is heterogeneous, then the type of a[i] can vary depending on the value of i, since different array positions can have different types. Because the value of i cannot (always) be known at compile time, the type of a[i] cannot be known either. But when a is homogeneous, the type of a[i] is constant.
A dynamically-typed language will have to check the type of a[i] at run-time anyway, so it has nothing to gain by making a homogeneous.