Weak typing ≠ dynamic typing ≠ duck typing

In the context of programming languages, many people seem to consider at least two of the terms “weak typing”, “dynamic typing” and “duck typing” to be synonymous. However, each of these has a completely different meaning and they can independently apply or not apply to a given language.

A swimming duck
Can a dynamic duck be strong?

Weak/strong typing

Definition. A language is strongly typed if it makes use of its type system to prevent logical errors, make APIs easier to understand, etc. A language is weakly typed if it treats types only as an implementation detail and often allows implicit conversions. Note that this is really a continuous scale, not a binary distinction.

Strongly typed languages include Haskell, Rust, Nim, Julia, Python, Ruby, …

Weakly typed languages include C, JavaScript, PHP, Tcl, …

If you object to C being called a weakly typed language, consider the following example:

#include <fenv.h>
#include <stdio.h>

int main() {
  int a = 42;
  int b = &a;
  int c = FE_TOWARDZERO;
  int d = stdout;
  printf("%d\n", a * b + c / d);
  return 0;

Here we have three variables representing completely different things — one is an integer, one is a pointer, one is a flag for the floating point environment and one is a file handle. However, C allows us to declare them all as int and perform arithmetic on them.

Dynamic/static typing

Definition. A language is statically typed if the types of all values are known before running the program. A language is dynamically typed if it's not statically typed.

Statically typed languages include C, Java, Haskell, Nim, Rust, …

Dynamically typed languages include Python, Common Lisp, Ruby, JavaScript, PHP, …

Note that a statically typed language doesn't necessarily require type declarations to be present on all variable declarations. Consider the following Nim program:

import std/strutils

stdout.write "What's your height in meters? "
let height = stdin.readLine.parseFloat
stdout.write "What's your weight in kilograms? "
let weight = stdin.readLine.parseFloat
let bmi = weight / (height * height)
let diagnosis = "Your BMI is " & $bmi
stdout.writeLine diagnosis

There are no type annotations in this program, yet all types are known and checked at compile time thanks to type inference.

Duck typing

Definition. A language is duck typed if procedures that work on generic types don't type-check until instantiation time.
Example. Duck typed languages include Python, Ruby, Smalltalk, C++, Nim, …

The definition may sound a bit cryptic, so let's see a concrete example. Consider the following Haskell code:

middle :: a -> a -> a -> a
middle x y z
  | x <= y && y <= z = y
  | z <= y && y <= x = y
  | y <= x && x <= z = x
  | z <= x && x <= y = x
  | otherwise = z

Trying to compile this produces the following error:

.code.tio.hs:3:5: error:
    • No instance for (Ord a) arising from a use of ‘<=’
      Possible fix:
        add (Ord a) to the context of
          the type signature for:
            middle :: forall a. a -> a -> a -> a
    • In the first argument of ‘(&&)’, namely ‘x <= y’
      In the expression: x <= y && y <= z
      In a stmt of a pattern guard for
                    an equation for ‘middle’:
        x <= y && y <= z
3 |   | x <= y && y <= z = y
  |     ^^^^^^

This is because we've declared that middle works for any type, but in fact it only works for types that support comparison. We can fix this by declaring that a has to be an ordered type:

middle :: Ord a => a -> a -> a -> a

Now let's implement the same function in C++:

template <typename T> T middle(T x, T y, T z) {
	if ((x <= y && y <= z) || (z <= y && y <= x)) {
		return y;
	if ((y <= x && x <= z) || (z <= x && x <= y)) {
		return x;
	return z;

C++ doesn't complain. When we try to use the function on a non-ordered type, that's when we're going to get into trouble, but otherwise it's cool. This makes C++ a duck-typed language, even though it's statically typed.

To comment, use this mailing list.