Static vs dynamic type checking
The type system is one of the fundamental elements which make up a programming language. There are two main categories under which most languages fall into: static and dynamic type checking. This taxonomy is orthogonal to that of programming paradigms such as object-oriented and functional.
Static type checking
A strong type checking system mandates that every value in the program belong to one supported type in the program. If the compiler cannot infer the type of any given value, it will throw an error. For this reason, it is possible for statically-typed languages to detect type errors at compile time, which provides benefits later when the program is run. The fact that the program passes the compilation phase does not imply that the program is correct though, as there may be other kinds of errors in it. An interesting topic related to static type checking is type inference, which is employed by SML and its derived languages (link to article about type inference and Hindley-Milner).
Dynamic type checking
Dynamically-typed languages skip the type checking phase and the program is executed straight away. It is usually the case that languages employing this type checking are not compiled but rather interpreted, such as Ruby, Javascript, Python, Scheme (the reality can be more complex than written here, but a detailed treaty is out of scope). This means that the class of errors which may have likely been detected with static type checking will go undetected until the running program incurs in them and crashes or even worse it executes wrongly. Although dynamically-typed languages give up program safety, they gain in flexibility; in fact, they allow the programmer to define heterogeneous data structures and abstractions which would otherwise be clumsy if not possible in a statically-typed language. For example, it is possible in Scheme to define lists of any element regardless of the type. A valid list can be (1 "2" #t)
. This is not possible in Haskell for example, where each element of a list must conform to the specified type of the list:
multiplyByTwo :: [Int] -> [Int]
multiplyByTwo xs = map (*2) xs
which when executed at the command line gives
> multiplyByTwo [1, 2, 3, 4]
[2, 4, 6, 8]
One misconception to be beware of is that dynamic type does not mean that values do not have types, they are indeed typed, just their type is inferred while the program is running rather than by the compiler.
Strong vs weak type
An honourable mention is worth giving to the strong vs weak type classification, since it is often wrongly used interchangeably instead of static and dynamic type checking. Actually, there is not a definitive definition of strong and weak type. Most languages, even dynamically-typed ones, are strongly typed. Weakly-typed languages are those languages which perform some kind of implicit type conversion. C and C++ are the most well-known languages which can be defined weakly-typed.
Overview of some popular languages
As a closing point I will provide an overview of some popular languages in relation to their type system and programming paradigm. The following table provides a rough classification of some popular languages:
dynamically-typed | statically-typed | |
---|---|---|
object-oriented |
Ruby |
Java, C++ |
functional |
Scheme, Clojure, Elixir |
SML, Haskell |
Note that it is usually not entirely correct that a language follows exclusively one single paradigm as most popular languages are multi-paradigm. Nonetheless there is usually one paradigm which is primary to a given language. Some other languages position in the middle and allow both styles interchangeably, such as Scala and Kotlin. Some languages have not been included as their classification would be ambiguous. Python is weakly-typed and supports both paradigms, even though it is more apt to either a procedural or object-oriented style. Javascript is weakly-typed and draws a lot from the functional approach, even though it does support some kind of object-oriented form.