Gradual typing

Can we add static types to a dynamic language? In some cases, we can; in others, it's difficult or impossible.

The most obvious problem is eval and similar dynamic language features. Evaluating 1 + eval("2") in Python gives us 3. But what does 1 + eval(read_from_the_network()) give us? It depends on what's on the network at runtime. If we get an integer, that expression is fine; if we get a string, it's not. We can't know what we'll get until we actually run, so we can't statically analyze the type.

The unsatisfying solution used in practice is to give eval() the type Any, which is like Object in some OO languages or interface {} in Go: it's the type that can have any value. Values of type Any aren't constrained in any way, so this effectively removes the type system's ability to help us with code involving eval. Languages with both eval and a type system have to abandon type safety whenever eval is used.

Some languages have optional or gradual typing: they're dynamic by default, but allow some static annotations to be added. Python has added this feature recently; TypeScript is a superset of JavaScript that adds optional types; Flow does static type analysis on regular old JavaScript code. These languages provide some benefits of static typing, but they can never provide the absolute guarantees of a truly static language. Some functions will be statically typed and some will be dynamically typed. The programmer always needs to be aware of (and wary of) the difference.

This is one section of The Programmer's Compendium's article on Types, which contains more details and context.