Static/Dynamic Typing? Strong/Weak Typing?

In a book I am currently reading (I do not want to name the book here, because I do not want to discredit it; but it doesn't matter anyway) I stumbled upon the following paragraph about typing in programming languages:

The terms strong and weak typing are sometimes used to refer to statically typed and dynamically typed languages respectively.

Albeit being mixed up quite often, this is just plain wrong. These are totally different concepts (well, "totally" is probably a bit exaggerated, but I'm trying to make a point here), so let me clear things up.

Static and Dynamic Typing

Let's consider static and dynamic typing first, because the differences are quite easy to see. In a statically typed language like Java, the type of a variable (as indicated in the variable's declaration) is fixed, so the variable may only hold values of this specific type. For example, consider the following Java code:

int foo = 13;

The variable foo is declared of type int, so foo can only hold values of this type. The following snippet is therefore illegal and produces a compiler error:

int foo = 13;
foo = "hello, world!";   // type 'String' does not match foo's declared type

In a dynamically typed language like Python however, a variable's type may change and depends on the value the variable contains at a specific position in the code. For example, the following snippet is perfectly valid Python code:

foo = 13    # foo is now of type 'int'...
foo = "hello, world!"    # ... and now it is of type 'str'

If you insert type(foo) after each assignment above, you will see that the type of foo changes depending on the value it is currently holding.

Summing up, you can say that in a statically typed language the types are bound to variables, whereas in a dynamically typed language they are bound to the values. Both concepts have their own advantages and disadvantages. I myself definitely favor statically typed languages over dynamically typed ones, because if variables have a static type many errors can be detected at compile time, whereas with dynamic typing they are not detected until run time. However, programs written in dynamically typed languages tend to be more concise and less verbose.

By the way: not having to type a variable's type name does not mean that the language is dynamically typed; some programming languages like Scala use type inference (where possible) to deduce the type of a variable automatically. For example, the compiler is able to find out that foo has to be of type String in this Scala code snippet:

var foo = "hello, world!"

However, Scala is a statically typed language like Java, so foo may only hold String values here.

Strong and Weak typing

Unfortunately, there is not a single perfect definition of strong and weak typing, and it is rather a graduation than a black/white classification. I will try to explain the differences by giving an example that shows how strongly and weakly typed languages differ. In general, a more strongly typed language makes it harder to "bypass" the type system, i.e. to use operations on "wrong" data types (there are other criteria for a language to be considered stronly or weakly typed, but I think this one is the most important). Therefore, strongly typed languages are considered to provide a higher type safety than weakly typed languages.

We will now look at a code snippet written in C, a statically typed language that is considered weakly typed (see my point here?). If you write the following:

int foo = 13;
float bar = 3.;
float bazz = foo + bar;

the value of bazz becomes 16.0, as you would expect. The compiler added an implicit type conversion to convert the int value stored in foo to a corresponding temporary value of type float. However, if you write the following lines of code:

int foo = 13;
float bar = 3.;
float bazz = *((float *)(&foo)) + bar;

the value of bazz becomes just "random" garbage. So, what did I do here? By writing ((float *)(&foo)) I took the address of foo and interpreted it as an address to a value of type float (using the explicit cast (float *)). By dereferencing this address using the * operator the value at this location in memory is treated as a value of type float, although it is actually an int value (the bit sequence at this location represents the int value 13; this is, however, not the representation of the float value 13). Thus, if you add this value to another value, the result is not what you would expect.

This example might look a bit contrived, but it shows an important point: a weakly typed language allows you to circumvent type safety, whether it makes sense or not. A more strongly typed language prevents you from doing so. For example, in Java a type cast is only possible if source and target type are in an inheritance relationship (or convertible in case of primitive types), and a cast results in a run time excpetion if a type safety violation is detected.

tl;dr

Never mix up static/dynamic typing with strong/weak typing; they are different things. A language is considered statically typed if variables have a fixed type and may therefore only hold values of this specific type, whereas in a dynamically typed language a variable's type may change. On the other hand, strongly typed languages provide higher type safety than weakly typed languages by restricting the ways in which you can access the values in memory.