static typing
expression and types are checked at compile time for correctness
the type of a value is determined by the type of the variable it was read from
can be explicit, implicit or mixed
e.g. C, Haskell, Java
static monomorphic typing
variable is associated with a single, fixed type
a.k.a lexical typing
static polymorphic typing
variable associated with a type scheme which contains type variables
dynamic typing
expressions and types are checked at runtime
untyped variables and functions
often scripting and prototyping languages are dynamically typed
values have types not variables
e.g. Lisp, Python, JavaScript
type encoding: boxing
type information is stored in the header of the value structure
numeric code for each object
type check operation: *ptr == #x
has high overheads, type check = memory access, each object needs an extra word of storage
type encoding: tagging
type information is stored in the pointer to the value
optimisation of boxing for most common types
type encoding: big bag of pages
type information depends on the region of memory the value is in
multiple homogenous pages
only practical with a few fixed types
type check operation: lower_bound <= ptr <= upper_cound
high bit tagging
low bit tagging
untyped
up to the programmer how to interpret a value
all values are presented as a machine byte or word
assembly langs still treat floats differently to ints as CPUs have seperate floating point and non-floating point registers
e..g ASM, BCPL, Forth
strong vs weak typing
reflects the level of automatic conversion permitted
C is weakly typed, allows code like double x = 1; and the int value get coerced to a double before assignment.
languages like Rust are more precise and will not automatically coerce types.
type coercion
an implicit change of type, done automatically by the compiler
e.g. when given 1.0 + 2, python will convert 2 to a float automatically
this can sometimes lead to unexpected behaviour
type casting
explicit change of type.
e.g. double = (double)2/1;
mixing types: dynamic typing
the compiler must perform type checks during runtime before calling a function and if the types are wrong then perform coercions or error.
adds a lot of runtime overhead
mixing types: static typing
the compiler will determine the types of a and b and do any coercions and choose the correct function at compile time, no runtime checks are needed
mixing types: untyped
the operation is performed, there are no types to check or coerce
mixing classes: dynamic lookup
the compiler generates code to
* choose the correct method to call on the current value
* call the chosen method
adds runtime overhead
mixing classes: static lookup
the compiler can’t just determine the types and find the appropriate method at compile time because even in a static language a variable can be reassigned to a subtype of the original assignment, possibly changing the function that should be called.
duck typing
highly dynamic kind of typing
to eval a.foo() the runtime examines the current value of a to see if there is a foo method defined on it and calls it if it finds one
manifest typing
the program code includes the types of variables
e.g. C
implicit typing
the compiler infers any types it needs as much as it can
e.g. in Haskell
inc x = x + 1
where Haskell determines the tpye of inc to be Num a => a -> a
mixed typing
a variables type can be infered by its usage
e.g. in Rust
fn fix(x: f64) -> i32 {}
let y = fix(z);
Rust will determine the type of z as f64 and y as i32
Rust allow most type annotations to be implicit but requires some to be manifest, function declarations must be explicit
overloading
multiple function definitions with the same name but different argument types
also known as ad-hoc polymorphism
compile time syntactic method selection
name mangling
when the compiler renames functions internally to differentiate between overloaded functions