XTCLANG GUIDES
Understanding Types.
xtclang
types
objects
type-system
strong-typing
Authored by: Abby Sassel, Cameron Purdy
Last Edited: @June 12, 2024
About this guide
Learn about the main components of Ecstasy's type system and its unique features. By the end of this guide, you will better understand the features and benefits of Ecstasy's type system and how they can be applied.
Topics
- Defining the term "type system"
- Characteristics of Ecstasy's type system
- Defining types in Ecstasy
Target Audience
If you would like to learn how to define and reason about types in Ecstasy, this guide on Ecstasy’s Type System is for you!
It is helpful to have an understanding of Ecstasy’s syntax, structure and execution model prior to using this guide. For a primer, please refer to earlier guides here.
Defining the term "type system"
The Ecstasy type system, language, compiler, and core libraries are designed to work in harmony to securely and effectively deliver the capabilities described in this guide. The term "type system" is often simultaneously vague and overused, so this chapter is intended to bring the concepts down to earth, and to illustrate the purpose of the design, and the value that it delivers to the programmer.
A language's type system refers to the abstractions and their classifications that a language provides around the raw data that the language machinery manages on behalf of the programmer. In a good design, those abstractions and classifications are used to efficiently avoid or constrain undesirable behavior, and to simplify and streamline the actual desired behavior. In Ecstasy there is a tangible TypeSystem class so, besides that brief explanation, we'll avoid using the generic term in this guide.
At its core, programming deals with bits - ones and zeros. However, a type system elevates this binary world to a higher abstraction level. It enables developers to work with more complex constructs like Int
, Boolean
, and even custom objects like ShoppingCart
, all governed by a set of rules that define permissible operations.
Characteristics of Ecstasy's Type System
The following characteristics of Ecstasy's type system give it certain advantages over other C-family languages. Let's explore each characteristic in turn.
Strongly typed
In Ecstasy, object types are indicated explicitly and checked stringently. For example, incorrectly assigning an Int
value to a String
variable is guaranteed to fail and be caught by the type system.
Statically typed
The Ecstasy type system enforces rules at compile time (statically). Rules that cannot be enforced at compile time are enforced at load-and-link-time instead. Still other rules are enforced at runtime as a final resort.
Carries complete runtime type information
Every object's class and reference type are known at runtime, with complete structural information accessible via reflection.
Furthermore, the compiler embeds specific compile-time type information that cannot otherwise be reconstructed at runtime; for example, when two objects are being compared for equality, their types are included in the compiled the code, so that the runtime can make use of that information.
Type information is not erased, such as in the case of generic data types; this is referred to as a "reified type system", or as "reified types" -- which literally means "types made real".
Based on nominative types
In Ecstasy, types are identifiable and constructed through their names, like a Point3D
class extending a Point
class by name.
To construct an instance of a type, the name of class being instantiated must be specified, such as new Point(0,0)
. And so on.
Duck typing for interfaces
The Ecstasy type system also supports "duck typing", but only for interfaces. This term is a play on the saying: "If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck." What this means is that if a class fulfills an interface's requirements without explicitly declaring it, it's considered to implement that interface - a pragmatic approach in real-world scenarios.
Duck typing is useful for representing functionality from an existing class as an interface -- especially when you aren't able to change the class to add an explicit implements
clause. It turns out that this is quite common in the real world, such as when that class is in a module that is already deployed to production, or when that class is in someone else's module and you want to or need to work with that module -- but you're not able to change their class in their module.
No structural typing
Other than the explicit duck typing support for interfaces, Ecstasy does not support structural typing. This just means that two classes are not considered to be of the same type just because they happen to have the same number of properties of the same types, for example.
Type algebra
Ecstasy supports a type algebra that includes parameterized types; union, intersection, and difference types; tuple types; explicitly immutable types; access-controlled types; and annotated types.
Everything is an object
In Ecstasy, "everything is an object", and therefore everything is of an object type. There is no separate "primitive type system" that the language pre-defines and hard-codes, like in C++, Java, or C#. This Ecstasy type system design is called the "turtles type system", because it's turtles the whole way down; it has no external references or dependencies on types outside of its own object type system.
For example, every bit is an object of the Bit
class. Every integer is an object, of the appropriate Int
` class. And so on.
Separation of types and classes
Ecstasy separates types from classes. The interaction with objects is always through references, with types being part of the reference, and classes part of the object.
The class of an object is what the object actually is. But in Ecstasy, an object can never be directly "touched" or "held" by the programmer; instead, every interaction with an object is via a reference to that object. When we "assign an object to a variable", what we really mean is that we are storing a reference-to-that-object in that variable. When we "set a property to a value", what we are actually doing is storing a reference-to-the-value in that property (and by "value", we mean "some object"). When we "pass an object to a method", we are actually passing a reference-to-the-object to the method. Even from within an object, the object's own interactions with itself are performed via its this
reference; an object can't even touch itself! But in each case, it is the reference that has a type; the type is part of the reference, while the class is part of the object. And since this is Ecstasy, everything is an object, including every reference.
Defining a "type" in Ecstasy
So now that we have defined what a type system is, let's explore what a type is from three different perspectives: it's composition, capabilities and constraints.
Composition
A type is about its composition. Ecstasy types are defined by how they are formed:
Nominative
The concept of nominative types in Ecstasy means that a class like Int
directly defines a corresponding type with the same name. This concept extends to other constructs like class
, interface
, mixin
, const
, enum
, module
, package
, service
, and typedef
.
Access
With a nominative type, the default type view of the class is the public interface, but it is possible to specify any of four pre-defined types exposed by a class: public
, protected
, private
, and struct
. The first three of these are self-explanatory; the struct
access refers to the structure type of the class, which provides the underlying passive field storage for each property of the class that may hold a value. For example, the type "(private Person)
" refers to the "private view" of the Person
class.
Immutable
In Ecstasy, immutability can be explicitly stated. A type like Map<String, Int>
might be mutable or immutable, but immutable Map<String, Int>
clearly denotes an immutable Map
.
Parameterized
Ecstasy supports types that are parameterized by other types. For example, the List
interface is declared as parameterized by an Element
type, so parameterizing the nominative List
type with the nominative Int
type results in the List<Int>
parameterized type.
Tuple
A Tuple
is a specially parameterized type form, which supports zero or more fields, each of which is identified by a zero-based index and is defined with its own type. For example, a Tuple<Int, String>
is a tuple with two fields (also called a pair), with field [0]
being an Int
and field [1]
being a String
.
Union
A union type is used to refer to one of two different types, as in "type A or type B". For example, String
is a const
class, and Nullable
is an enum
class. If a variable can hold either the Null
value or a String
, we say that the variable type is a union Nullable|String
, pronounced as "nullable or string". Because that "Nullable|
" prefix is so common, there exists a short-hand that means the same thing: String?
.
Intersection
An intersection type is used to refer to the combination of two different types, as in "type A plus type B", or "both type A and type B". As mentioned above, List
is parameterized by Element
. There is also an interface called Freezable
; an Element
that is also known to be Freezable
can be asserted to have the intersection type Element+Freezable
; you can see this type at work in the ListFreezer
implementation.
Difference
A difference type is used to refer to the absence of a type, as in "type A minus type B", or "type A and not type B". For example, the toChecked()
method on Int64
returns the (Int64 - Unchecked)
type, which means "this type, and not Unchecked
".
Capabilities
A type is about its capabilities. Ecstasy types are defined by what they can do.
A type contains type members. To simplify slightly, the members are the properties and the methods of the type.
If the type of a reference has a readable property (the property, exposed as a Ref
), then anyone with that reference can use it to read the value of that property on the object.
If the type of a reference has a writable property (the property, exposed as a Var
), then anyone with that reference can use it to read and write the value of that property on the object.
If the type of a reference has a method, then anyone with that reference can use it to invoke that method on the object.
In each of these cases, it doesn't matter if the members are public
, protected
, or private
; these keywords are used for organization, and not for security. The actual type of the reference is what determines what is allowed to be done using the reference.
Constraints
A type is about constraints and guarantees. Here are the most common examples:
- If a property or a variable has a certain type, then only references of that type can be stored in that property or variable, and it is guaranteed that any reference obtained from the property or variable will be of that type.
- If a method or function declares a parameter of a certain type, then only arguments of that type can be passed, and from the method or functions point of view, it is guaranteed that the arguments will be of the defined types.
- If a method or function declares a return value of a certain type, then only values of that type can be returned by the code in the method or function, and from the caller's point of view, it is guaranteed that any returned values will be of the defined type.
- If a class defines a type parameter with a type constraint, then the class can only be parameterized by types that match that type constraint. For example, in the definition
class NumList<Element extends Number> extends List<Element>
, it is guaranteed that the typeElement
is aType<Number>
.
Summary
This guide introduced features of Ecstasy's type system and explored the composition, capabilities, and constraints of types in Ecstasy. We hope it gives you some confident to define, create and understand types in the Ecstasy language.
EXPLORE & LEARN
Ready to learn more?
Check out our latest guides, blogs and tutorials!