1. Introduction

A common misconception in the programming world is about the definitions of static vs. dynamic and strong vs. weak languages. As we’ll see, sometimes different people mean different things with those terms in the context of type systems. Generally speaking, type-checking is the process of ensuring a program meets the language’s type rules.

In this tutorial, we’re going to clarify those meanings, providing examples of strong, weak, static, and dynamic programming languages.

2. Static and Dynamic Types

In statically-typed programming languages, a compiler analyzes the source code and “tags” portions with a type. Such portions might be function declarations, variables, and values. It then uses those types to infer properties of the program’s behavior. In these languages, once the compiler assigns a type to a variable, it can never change.

For example, in Java, we have to define variables, giving them an explicit type:

class Main {
    public static void main(String[] args) {
        Integer i = 1;
        String s = "Baeldung";
    }
}

In dynamically-typed languages, type-checking occurs at run time (and not at compile time). This has significant implications on the safety of the program, as we have to run the code to get to know if we’re using an expression in a way we’re not supposed to. Furthermore, in dynamically-typed languages, the type of a variable can change during the execution of the program. Most scripting languages, such as Python, are dynamically-typed:

a = 10
print("Type: {t}, ID: {i}".format(t = type(a), i= id(a)))

a = "Baeldung"
print("Type: {t}, ID: {i}".format(t = type(a), i= id(a)))

In the example above, we define a variable named a and first set it to 10. Therefore, it was born as a number. Then, we set it to the string Baeldung. There’s no type error here and the language lets us do that. However, if we run the code, we’ll get two different types and IDs:

Type: <class 'int'>, ID: 140239585367616
Type: <class 'str'>, ID: 140239580979440

This shows that a changes its type throughout the execution of a program, pointing to two different values with two unrelated types.

Historically, one of the most common reasons against static type systems was the verbosity of the syntax. For example, the Java snippet is considerably more verbose than its Python counterpart. However, this has changed as type systems (and compilers) became more sophisticated. Modern languages, such as Scala or Rust, come with powerful type inference rules, letting us omit types as if we were writing in a dynamically-typed language.

3. Strong vs. Weak Types

Generally speaking, in a strongly-typed programming language, the type system forbids the application of any operation to any object that isn’t intended to support that operation. In other words, the type system is there to tell us whenever we invoke a method or function on a type that doesn’t define it.

Python, for example, is a strongly-typed programming language, even if it enforces its types at run time:

my_integer = 1
my_string = "Baeldung"
my_result = my_integer + my_string

The code snippet above, when run, fails with “TypeError: unsupported operand type(s) for +: ‘int’ and ‘str'”. The error message is quite clear and tells us we’re using an operator, +, in a way we’re not supposed to. Even if the error happens at run time, the language refuses to carry out a potentially harmful operation: an implicit cast. The error is a clear indication that we’re possibly doing something wrong.

On the other hand, in a weakly-typed language, we get no help whatsoever from the type system, even if we invoke an operation not defined on a specific type. In these languages, implicit conversions between data types happen under the hood. Let’s see an example in JavaScript:

let result = 10 + "20";
console.log(result);

The code snippet above outputs “1020″, showing that the runtime casts the number 10 into a string (this is the so-called type coercion) and pretends it to “20”.

In weakly-typed languages, the type system is more “flexible”, as it allows for implicit conversions. However, such flexibility comes at the cost of reduced trust in our code: we have to test it more intensively to make sure there are no subtle type errors.

The cast string to number we saw in the example above might be unexpected behavior and counter-intuitive, leading to subtle bugs. This is because it increases the risk of unpredictable errors that might be difficult to track. In strong type systems, on the other hand, compilers rule out type coercions at compile time, producing code that’s more predictable and easier to reason about.

4. Meaning Confusion

The goal of this article is to demystify the meaning of the terms “static”, “dynamic”, “strong” and “weak” in the context of type systems. The ambiguities are often on strong vs. weak, as those words lack a universally agreed-upon definition, as different authors and books define them slightly differently.

Furthermore, those properties aren’t black or white. A language might be more strongly-typed than another, but still have some elements of characteristics of a weak programming language. Similarly, in a statically-typed language, the type of some properties might be unknown at compile time.

To sum up, here are some definitions based on what we saw above:

  • Static: type checks happen at compile time
  • Dynamic: type checks happen at run time
  • Strong: the language throws an error (either at compile or run time) when we invoke an operation on a type that doesn’t support it
  • Weak: the language performs implicit conversions, trying to coerce the types to carry out the requested operation

This is a quick summary of the meanings, though there’s always room for a little nuance among various languages.

5. Conclusion

In this article, we clarified the meaning of the words “static”, “dynamic”, “strong,” and “weak” in the context of type systems and programming languages. We defined them and showed a few simple programming examples to support our definitions.


原始标题:Static vs. Dynamic and Strong vs. Weak Types in Programming