Skip to main content

Static Typing



Static type checking.


Type Lattice

Rascal has a static and a dynamic type system, which interact with eachother. The static type system is used by a type checker (not yet released) to predict errors and give warnings where possibly slipups have been made. The dynamic type system ensures well-formedness of data structures and plays an important role while pattern matching, since many algorithms dispatch on the types of values.

Rascal's static type system does not ensure that all functions will go right:

  • functions may throw exceptions.
  • functions may not be defined for the specific pattern which occur on the call site.

However, the static type system will produce an error when a function will certainly throw an exception, or when it is certainly not defined for a certain case. Also it catches some logical tautologies and contradictions which would lead to dead code.

The Rascal types are ordered in a so-called type lattice shown in the figure above.

The arrows describe a subtype-of relation between types. The type void is the smallest type and is included in all other types and the type value is the largest type that includes all other types. We also see that rel is a subtype of set and that each ADT is a subtype of node. A special role is played by the datatype Tree that is the generic type of syntax trees. Syntax trees for specific languages are all subtypes of Tree. As a result, syntax trees can be addressed at two levels:

  • In a generic fashion as Tree and,
  • In a specific fashion as a more precisely typed syntax tree. Finally, each alias is structurally equivalent to one or more specific other types.

Rascal does not provide an explicit casting mechanism (as in Java), but pattern matching can play that role.

The language provides higher-order, parametric polymorphism. A type aliasing mechanism allows documenting specific uses of a type. Built-in operators are heavily overloaded. For instance, the operator + is used for addition on integers and reals but also for list concatenation, set union and the like.


Some example can illustrate the above.

rascal>int I = 3;
int: 3

Since I is declared as type int, we cannot assign a real value to it:

rascal>I = 3.5;
|prompt:///|(4,3,<1,4>,<1,7>): Expected int, but got real
Advice: ||
rascal>num N = 3;
num: 3

Since N is declared as type num, we can assign both int and real values to it:

rascal>N = 3.5;
num: 3.5

Since all types are a subtype of type value, one can assign values of any type to a variable declared as value:

rascal>value V = 3;
value: 3
rascal>V = "abc";
value: "abc"
rascal>V = false;
value: false

We can use pattern matching to classify the actual type of a value:

rascal>str classify(value V){
>>>>>>> switch(V){
>>>>>>> case str S: return "A string";
>>>>>>> case bool B: return "A Boolean";
>>>>>>> default: return "Another type";
>>>>>>> }
str (value): function(|prompt:///|(0,150,<1,0>,<7,1>))
str: "A Boolean"
A Boolean
rascal>V = 3.5;
value: 3.5
str: "Another type"
Another type

In addition to these standard examples, it is interesting that all Algebraic Data Types are subtypes of type node. Let's introduce a simple Color data type:

rascal>data Color = red(int level) | blue(int level);

Unsurprisingly, we have:

rascal>Color C = red(3);
Color: red(3)

Due to subtyping, we can also have:

rascal>node ND = red(3);
node: red(3)

One example of the actual application of subtypes can be found in Count Constructors.


  • static types and names help with refactoring code, when parts of it have to co-evolve the compiler will point out unresolved changes.
  • static types and names help with making the code more readable. Each name is a documentation opportunity.
  • generic types are good for reusing generic functions and generic data
  • builtin relation types allow for hard optimizations under-the-hood which are not possible in a functional language
  • immutable data allows for co-variant generic collections (sets and lists)