XTCLANG GUIDES
How to work with Classes and Objects in Ecstasy
xtclang
class
object
abstract class
concrete class
class contents
Authored by: Abby Sassel, Cameron Purdy
Last edited: @February 10, 2024
About this guide
Understand the relationship between classes and objects in this introductory guide.
Topics:
- Object versus Classes
- Abstract and concrete classes
- Class contents
Target Audience
This guide is suitable for newcomers to the Ecstasy programming language who want to understand the basics of its object-oriented features.
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.
Objects versus Classes
Classes are the blueprints for objects.
If an object exists, it exists because there was a blueprint that was used to create it. As in Java and C#, if an object of some class Person
exists, then it probably exists because some code that contains the expression "new Person()
" was executed. There are other ways to create objects, but for now, let's keep it simple, and assume that they were all created using new
.
A blueprint for an object is called the object's class. And new
-ing an object is called instantiation, as in:
- "... the object was instantiated from the class"
- "instantiating the object ..."
- "... a new instance of the class"
- "creating an instance of the class ..."
A concrete class is a set of blueprints that is complete enough to be used to instantiate an object. An abstract class is a set of blueprints that lacks sufficient information on its own to instantiate an object. But don't think of an abstract class as a failure, like not finishing your homework! Instead, think about an abstract class as some useful piece of design that you can use in a more complete design; in other words, abstract classes are reusable building blocks.
There are a few different reasons why a class may be abstract:
- A class that is declared with the
interface
keyword is always abstract. - The class representing an entire enumeration, such as
Boolean
, is always abstract. On the other hand, the actual values of the enumeration, likeTrue
andFalse
for theBoolean
enumeration, are concrete classes; specifically, enumeration values are singleton const classes, each of which is a sub-class of the enumeration class. - A mixin is abstract, because it needs to be mixed into another class in order to form an instantiable class.
- A class can be annotated with the
@Abstract
annotation in order to make it explicitly abstract. - Instead of producing a compilation error, the compiler may mark a class as abstract if the class is missing some necessary design information, but no code is being compiled that actually attempts to instantiate it.
There are two truisms related to objects and classes:
- All objects are instantiated from concrete classes; if an object exists, it is an instance of a concrete class.
- Everything is an object; even a class is an object. But this is simpler than it sounds: In Ecstasy, anything that exists and you can do something with is an object. And since classes exist, and since you can do something with them, they must be objects.
And finally, there are only a few ways that classes themselves are used:
- Classes are used to instantiate objects, such as in the code:
return new User(id);
. - Classes are used for organization. That means that the name of a class can be used to tell the compiler where to look for another name. For example, the compiler understands that
FPNumber.PI
refers to the named constantPI
that is defined within the classFPNumber
. - Classes are actual objects, such as the name
Boolean
in the code:Class boolclass = Boolean;
. - A singleton class makes its singleton instance available via the name of the class. Here are two obvious examples that we have already discussed:
- A module class:
Module m = HelloWorld;
(the name "HelloWorld
" identifies the singleton instance of the module) - The values of an enumeration:
Boolean value = True;
(the name "True
" identifies the singleton instance of the enum value) - The name of a class can be used to indicate the corresponding type. In the example immediately above,
Boolean value = True;
, the class nameBoolean
is used to specify the type of the variable. We'll cover the concept of types in the next chapter.
Class Contents
A class in Ecstasy contains various elements: type parameters, constructors, methods, functions, properties, constants, typedefs, and inner classes. We can categorize these into two main groups:
class-level(static, no this required) | object instance-specific(this required) |
type parameters | |
constructors | |
functions | methods |
constants | properties |
typedefs* | typedefs |
static child classes | virtual child classes |
There's a lot to explain here, and there's no simple way to explain it. Let's start by illustrating the "no this
required" column, which is the simple part of this topic:
class Example {
static Int addOne(Int n) {
return n+One;
}
static Int One = 1;
typedef String as Text;
static const Point(Int x, Int y);
}
Notice that each one of these items is unrelated to its containing class, other than happening to exist syntactically within it:
- The function
addOne
operates only on the value passed to it, and it can be called without having an instance ofExample
. And yes, the nameExample.addOne
references an actual function object (of typeFunction<<Int>, <Int>>
). - The constant
Example.One
has a value unrelated to any instance ofExample
. - The typedef
Example.Text
is always aString
, and is not based on or related to any instance of theExample
class. - The const class
Example.Point
can be constructed without having an instance ofExample
, and it does not capture (i.e. it does not have) a reference to any instance of theExample
class.
Each of these items can be referenced directly without having an instance of Example
. This is an Example
-- no pun intended! -- of a class being used as a means of organization ("somewhere to put stuff"), and is not some highfalutin Object Oriented concept. In some ways, Example
is being used just like a package
is often used: It is an organizational container of other stuff. As we explained in a previous section, classes within a package can be pasted inside the body of the package code itself, just like the Point
above is found inside of Example
. In fact, it would be perfectly legal in Ecstasy to simply change class
to package
in the Example
code above!
You can see from this Example
that you can place these kinds of items within a class, and access them either from code within the class, or by specifying the class name. But these items are not part of the object instances of the class. So with this simple part of the topic out of the way, let's move on to the more interesting topic of the things that are related to actual instances of the class.
Instance-Specific Class Contents
Contents that are instance-specific vary between different instances of a class. Accessing these contents requires an instance of the class.
const Point(Int x, Int y);
Breaking this down:
- The form of the class is a
const
, which defines a class whose objects are immutable by the time that they complete construction. - The name of the class is
Point
, likely referring to a Cartesian coordinate system. - No explicit building blocks are used to define the class. However, all classes automatically implement
Object
, and allconst
classes automatically implement theConst
interface, which is itself composed ofOrderable
,Hashable
,Comparable
,Stringable
, andCloneable
. - The declaration ends with
(Int x, Int y)
, which is short-hand notation for declaring two properties and defining an initializing constructor; the long-hand equivalent may be more familiar:
const Point {
Int x;
Int y;
construct(Int x, Int y) {
this.x = x;
this.y = y;
}
}
The important thing to keep in mind at this point is that each property on an object is itself an object that holds (or at least provides) a value; it is not a field. Let's add another property that measures the area of the region from the origin (0, 0)
to the point:
const Point(Int x, Int y) {
Int areaFromOrigin {
@Override
Int get() {
return x.abs() * y.abs();
}
}
}
This new property whose type is Int
and whose name is areaFromOrigin
looks a lot like a class inside of the Point
class -- and it is! Every read-only property automatically extends a class that implements the Ref
interface, and every read/write property automatically extends a class that implements the Var
interface, which itself extends the Ref
interface. While the class that implements read-only and read/write properties is unknowable, it must exist -- because properties exist, and they are objects, and objects come from classes. As with the Module
implementation code in the previous chapter, the linker is responsible for automatically providing an implementation of the Ref
and Var
interfaces for us.
The above example code is a bit wordy, though. Fortunately, there is a short-hand syntax for defining a single method (in this case, get
) on a property:
const Point(Int x, Int y) {
Int areaFromOrigin.get() {
return x.abs() * y.abs();
}
}
To further illustrate, let's introduce a sub-class that works in three-dimensional space. Before discussing the example, we do need to apologize for abusing sub-classing in this manner; if we didn't need an easy-to-explain example, we would never have done this:
const Point3D(Int x, Int y, Int z)
extends Point(x, y) {
@Override
Int areaFromOrigin.get() {
// technically this is volume, not areareturn (x * y * z).abs();
}
}
There are a few important reasons for showing this example at this point:
- To illustrate the use of the
@Override
annotation, which is required when overriding an instance member (e.g. method or property) of a class; - To show how one constructor can delegate to another using the short-hand notation; and
- To have an excuse to show the long-hand form of the constructor:
construct(Int x, Int y, Int z) {
this.z = z;
construct Point(x, y);
}
There are various rules about what you can and cannot do from a constructor, and these rules are stricter in Ecstasy than in most languages. In an Ecstasy constructor, the object being instantiated does not yet exist; the this
that the constructor has access to is not a Point
object, but is a structure that will be used to instantiate the Point
object after the constructors are all finished. The structure contains only the fields (the raw memory storage) for the Point, so the code this.z = z
in the constructor is not setting a property, but is simply storing the value in the structure's field in memory. Similarly, the constructor cannot call any of Point's methods on this
, because no Point
object exists during the constructor. We'll get into more details about constructors later on, but if you're playing around with code examples, these details will hopefully save you some frustration.
Methods in Ecstasy follow a syntax that is based closely on Java and C#. Continuing the Point
example from above, let's add two methods:
void print() {
@Inject Console console;
console.print($"point={this}");
}
Point double() {
return new Point(x*2, y*2);
}
If you're paying attention, we have now created a huge mess, because Point3D
will return a Point
from the double()
method. Or does it? There's a subtle detail here that needs to be explained: When the class Point
has a method that says it takes or returns a Point
, the compiler assumes that the method does not actually take or return an object of the exact class Point
, but rather the method takes or returns an object of the current class. So on Point3D, we need simply to add this implementation:
@Override
Point3D double() {
return new Point3D(x*2, y*2, z*2);
}
This behavior is called auto-narrowing: The Point
type when used in a declaration on the Point
class will be assumed to automatically narrow on sub-classes. If that assumed behavior is wrong, then the syntax to tell the compiler that we meant the exact Point
class (and not the auto-narrowing class) is to use the type Point!
, as in, "I really mean Point
!" And remember that the @Override
annotation on the method is required here; to avoid common programming errors, overriding a method in Ecstasy is a conscious and explicit decision. We'll cover these topics in more depth when we get to the virtual behavior and the virtual child class topics.
For now, though, we hope you've enjoyed this guide highlighting the role of classes, the significance of concrete and abstract classes, and how they can be used.
EXPLORE & LEARN
Ready to learn more?
Check out our latest guides, blogs and tutorials!