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.