Subtyping

In Erg, class inclusion can be determined with the comparison operators <, >.

Nat < Int
Int < Object
1... _ < Nat
{1, 2} > {1}
{=} > {x = Int}
{I: Int | I >= 1} < {I: Int | I >= 0}

Note that this has a different meaning than the <: operator. It declares that the class on the left-hand side is a subtype of the type on the right-hand side, and is meaningful only at compile-time.

C <: T # T: StructuralType
f|D <: E| ...

assert F < G

You can also specify Self <: Add for a polymorphic subtype specification, for example Self(R, O) <: Add(R, O).

Structural types and class type relationships

Structural types are types for structural typing and are considered to be the same object if they have the same structure.

T = Structural {i = Int}
U = Structural {i = Int}

assert T == U
t: T = {i = 1}
assert t in T
assert t in U

In contrast, classes are types for notational typing and cannot be compared structurally to types and instances.

C = Class {i = Int}
D = Class {i = Int}

assert C == D # TypeError: cannot compare classes
c = C.new {i = 1}
assert c in C
assert not c in D

Subtyping of subroutines

Arguments and return values of subroutines take only a single class. In other words, you cannot directly specify a structural type or a trait as the type of a function. It must be specified as "a single class that is a subtype of that type" using the partial type specification.

# OK
f1 x, y: Int = x + y
# NG
f2 x, y: Add = x + y
# OK
# A is some concrete class
f3<A <: Add> x, y: A = x + y

Type inference in subroutines also follows this rule. When a variable in a subroutine has an unspecified type, the compiler first checks to see if it is an instance of one of the classes, and if not, looks for a match in the scope of the trait. If it still cannot find one, a compile error occurs. This error can be resolved by using a structural type, but since inferring an anonymous type may have unintended consequences for the programmer, it is designed to be explicitly specified by the programmer with Structural.

Class upcasting

i: Int
i as (Int or Str)
i as (1..10)
i as {I: Int | I >= 0}