The dynamic type and mixed typing

Alore programs may freely mix static and dynamic typing. You can do this in a few different ways:

Note that local variables are different. If the enclosing function is dynamically-typed, the type checker ignores them (defaulting to dynamic typing). If the enclosing function has a type signature, the type checker infers the types of local variables.

The type dynamic

If a variable has type dynamic, it is dynamically typed and no operations on it are checked statically. Consider this example:

var d as dynamic
d = 'x'
d = 1
d + 'y'           -- Runtime error

Since d has type dynamic, the type checker does not give any errors. However, the program generates an exception at runtime.

Implicit dynamic types

Global variables, member variables and functions are given dynamic types if they have no other types.

The following global variable definitions are equivalent:

var Foo
var Foo as dynamic

The following method signatures are equivalent:

def foo(a, b)
  ...
end

def foo(a as dynamic, b as dynamic) as dynamic
  ...
end

However, as the second method has explicit types (even though they are all dynamic), the body will use type inference. In the first method foo, the type checker will just ignore the body.

Type compatibility

Every type is compatible with the type dynamic, and vice versa. This allows you to smoothly integrate dynamically-typed and statically-typed code. Consider this example, which defines a dynamically-typed and a statically-typed function that call each other:

def D(n)
  if n == 0
    return 0
  else
    return S(n - 1) + 1
  end
end

def S(n as Int) as Int
  if n == 0
    return 1
  else
    return D(n - 1) * 2
  end
end

Note that S calls D with argument of type Int, while D expects dynamic; and vice versa. Neither needs any casts.

Assignment works in a similar way:

var n = 0 as Int
var d = 1 as dynamic
d = n   -- Ok
n = d   -- Ok

Running programs with mixed typing

When you run a program, the interpreter first erases all type declarations. The interpreter thus ignores static types at runtime. This happens behind the scenes and is not directly visible.

Here are a few examples of how erasing types works (we use ==> to mean erasure):

var a = 5 as Int ==>
var a = 5

def next(i as Int) as Int
  return i + 1
end
  ==>
def next(i)
  return i + 1
end

var a = [] as <Int> ==>
var a = []

Note that erasure does not remove normal casts, but it removes dynamic casts. For example:

var o = 1 as Object
var n = (o as Int)          -- Normal cast
var d = (o as dynamic)      -- Dynamic cast
  ==>
var o = 1
var n = (o as Int)
var d = (o)

Runtime type errors and mixed typing

Due to type erasue, some apparent type errors do not result in type errors at runtime:

var i as Int
var d = 'x'
i = d          -- Ok

The example assign a Str object to an Int variable. There is no static error since d has type dynamic; there is no runtime error since the type Int of i is erased before running the program.

A future Alore version will likely allow catching these kinds of type error. It will probably have two execution modes: a loose mode is equivalent to type erasure, while a strict mode detects cases like the example above.

Generic types and dynamic typing

Mixed typing also supports objects of generic types. You can create a generic instance such as Array in dynamically-typed code and pass it to statically-typed code, and vice versa:

var d = [1, 3] as dynamic
Sum(d)      -- Ok

def Sum(a as Array<Int>) as Int
  var sum = 0
  for n in a
    sum += a
  end
  return sum
end

You can also use dynamic as a type argument. In this case, dynamic is compatible with any type. For example, Array<dynamic> is compatible with Array<Str>.

Dynamic casts

You can use dynamic as the target type of a cast. This operation does nothing at the runtime, and it only selectively removes type checking from an expression. This allows you to bypass some restrictions of the type system:

var ao = [1] as Array<Object>
var ai as Array<Int>
ai = ao              -- Type check error
ai = (ao as dynamic) -- Ok (but be careful!)

Inheritance and mixed typing

A dynamically-typed class may inherit a statically-typed class, and override some of its methods:

class S
  def next(n as Int) as Int
    return n + 1
  end
end

class D is S
  def next(n)         -- Ok
    return n + 2
  end
end

This is useful especially in dynamically-typed programs that inherit statically-typed library classes. A dynamically-typed program or module does not need to care whether modules that it uses use static typing or not.

A statically-typed class can also inherit a dynamically-typed class, but this is less commonly useful. You can even change individual argument types to dynamic when overriding, while keeping others statically typed:

class S2
  def f(i as Int, s as Str)
    ...
  end
end
class D2 is S2
  def f(i as Int, s as dynamic)   -- Ok
    ...
  end
end