Static and Dynamic

Dear Computer

Chapter 3: Types

Static and Dynamic

The programming experience is significantly impacted by when a language's tooling considers types. Type systems fall into two broad categories: static type systems and dynamic type systems.

Static Type Systems

In a statically-typed language, the compiler performs typechecking at compile time, which means type errors are discovered before an executable is built. In addition to checking types, the compiler uses the type information to figure out what code to jump to on a method call or what machine instruction to invoke. Making these decisions about control flow at compile time means they do not to be made at runtime, leading to faster execution.

The executable that the compiler builds might not contain any type information whatsoever. Consider this C program:

C
int twice(int x) {
  return x + x;
}

The body of the function compiles down to this x86 assembly:

Assembly
movl %edi, %eax    # copy parameter, %eax = %edi
addl %edi, %eax    # %eax += %edi

You may not be familiar with x86. The important point is that types are absent from this code. The compiler considered the type of x and the return type when choosing the addl instruction, but all other type information has been stripped away. This code will run blazingly fast because it's not asking any questions about the types of the data.

Types aren't always completely removed from the compiled code. Several otherwise statically-typed languages provide mechanisms for programmers to perform their own typechecks at runtime. Java and JavaScript provide instanceof, Ruby is_a? and instance_of?, Python type and isinstance, and C++ dynamic_cast and typeid. If we are writing a polymorphic function and really need to know what the underlying type of a value is, we can query it in these languages.

Dynamic Type Systems

In a dynamically-typed language, the interpreter performs typechecking at runtime, just before an operation is performed. Our program may be riddled with type errors, but the interpreter will still run the code up until the first error. If an operation is not in the flow of execution, it will not be typechecked. Consider this Ruby program, whose then-block contains an illegal operation:

Ruby
if false
  7.remove()
else
  puts 'Respect was invented to cover the empty place where love should be.'
end

You can't remove 7. That's a type error. However, this script runs just fine because the then-block is not executed.

In most dynamically-typed languages, variables are not locked in to a single type. For example, the variable number holds values of two different types in this Ruby program:

Ruby
number = 7            # Integer
number = number.to_f  # Float

Therefore, in dynamically-typed languages, types are a property of values, not of variables. 7 and the value returned by to_f have a fixed type, but number does not.

Dynamic typing is implemented in two different ways: through type tags or through reflection. A type tag is metadata that is stored with a value that identifies the value's type. For example, this universal Box class pairs a value with a type tag:

Ruby
class Box
  def initialize(type, value)
    @type = type
    @value = value
  end
end

The type tag is queried to determine which operation to invoke. For example, this function uses the tag to arbitrate between an integer operation and a string operation:

Ruby
def rightmost(box)
  if value.tag == :int
    box.value % 10
  elsif value.tag == :string
    box.value[-1]
  end
end

If the tag reports an integer, we extract the rightmost digit. If the tag reports a string, we extract the rightmost character.

With reflection, the interpreter does not ask the value about its type at all. Rather, the interpreter asks the value about the operations it supports. In Ruby, we ask a value if it supports an operation using the respond_to? method. The rightmost method may be rewritten using this reflection method:

Ruby
def rightmost(value)
  if value.respond_to? :%
    value % 10
  elsif value.respond_to? :[]
    value[-1]
  end
end

The advantage of reflection is that rightmost now works for any values that support the % or [] operators, not just int and string. The type is irrelevant. Dynamic typing through reflection is called duck typing. Instead of asking an animal if it is a duck, we ask if it quacks, swims, or waddles. We don't care if it's a duck.

False Dichotomy

Programmers argue about whether static or dynamic typing is better. Both are capable of bringing joy and inflicting pain. When someone ranks them, they are telling you more about what kind of software they write and less about a universal truth. In fact, there's no need to pick just one of these systems. Pick them both. Use a dynamically-typed language when you want to get code up and running quickly. Use a statically-typed language when you want to build fast executables.

Also, statically-typed languages are not always completely statically-typed. Consider this Java program that creates an array of strings, makes an alias of the array reference of type Object[], and then attempts to update element 0 through the alias. Does this code typecheck, compile, and execute?

Java
String[] elements = {"tungsten", "tin"};
Object[] items = elements;
items[0] = "molybdenum";
System.out.printf("%s %s%n", items[0], items[1]);

It does all three. After all, items is an Object[]. A string is an Object, so assigning it to element 0 poses no problem. But what happens if you change the assignment to something that's not a string?

Java
String[] elements = {"tungsten", "tin"};
Object[] items = elements;
items[0] = new Double(-1);
System.out.printf("%s %s%n", items[0], items[1]);

This code compiles just fine, which means it passes the static typechecking tests. However, behind the alias items is an array of strings, and storing a Double in it should fail. Indeed, when you run the program, you encounter an ArrayStoreException. The Java designers identified that their design of the language made arrays vulnerable to type mismatches that could not be detected statically. So they added a dynamic typecheck that happens every time you assign a value to an array.

← Converting Between TypesExplicit and Implicit →