XTCLANG GUIDES
How to use class keywords in Ecstasy
xtclang
classes
class keywords
interface
mixin
enum
Authored by: Abby Sassel, Cameron Purdy
Last edited: @February 10, 2024
About this guide
This guide explores class keywords interface
, mixin
and enum
. By the end of this guide, you'll have a clearer idea of when and how to use Interfaces, Mixins and Enumerations respectively in the Ecstasy language.
Topics:
- Interfaces and super-interfaces
- Examples of mixins to dynamically modify or extend functionality
- Examples of enums to represent a fixed set of options
Target Audience
This guide is well-suited to programmers already familiar with classes, objects and inheritance, who'd like to learn more about advanced class declarations in Ecstasy.
If you'd like to understand more about the basics of Classes in Ecstasy first, we suggest you start with this How to define Classes in Ecstasy guide.
Interfaces
Interface definitions in Ecstasy are similar to Java, using the extends
keyword to specify super-interfaces, (equivalent to the use of :
in C#).
Here is the List
interface, for example:
interface List<Element>
extends Collection<Element>
extends UniformIndexed<Int, Element>
extends Sliceable<Int>
The use of multiple extends
clauses in the code above could be replaced with a comma-delimited list of super-interfaces; the two forms are equivalent:
interface List<Element> extends Collection<Element>,
UniformIndexed<Int, Element>, Sliceable<Int>
Mixins
Mixins allow classes to incorporate and compose functionality or attributes from other classes with more flexibility than traditional inheritance. Here is an example declaration of a mixin, ListFreezer
, whose job it is to add the ability to freeze (i.e. "make something immutable") to any List
:
mixin ListFreezer<Element extends Shareable>
into List<Element> + CopyableCollection<Element>
implements Freezable
It's important to clarify that this mixin isn't applicable to just any List
.It only applies to a List
class that has an Element
type that is Shareable
, and the into
clause specifies that the mixin can only be applied to a List
class that is also a CopyableCollection
. This is how a mixin defines its constraints on what classes it can be incorporated into. As you can see, though, the mixin is itself a form of class, specifying that it implements the Freezable
interface, for example. So when the mixin is incorporated into a List
, the resulting List
class will automatically pick up those additional building blocks, such as implementing the Freezable
interface. For now, don't focus on how this happens; we'll discuss those details in a subsequent chapter.
Here's another mixin, ListMapIndex
, which is used to add a fast-lookup index to a ListMap
. Look at its into
clause and its type parameter declaration — it specifies that it can only be mixed into a ListMap
, and more specifically, into one whose Key
type is Hashable
:
mixin ListMapIndex<Key extends Hashable, Value>
into ListMap<Key, Value>
Now, let's see how this mixin is used in the ListMap
class:
class ListMap<Key, Value>
implements Map<Key, Value>
implements Replicable
incorporates CopyableMap.ReplicableCopier<Key, Value>
incorporates conditional ListMapIndex<Key extends immutable Hashable, Value>
incorporates conditional MapFreezer<Key extends immutable Object, Value extends Shareable>
This example is more complex than the previous ones. Note that ListMap
does not require that its Key
be Hashable
, yet it incorporates ListMapIndex
, which as we just noted requires its Key
to be Hashable
! So let's examine that incorporates
line closely:
incorporates conditional ListMapIndex<Key extends immutable Hashable, Value>
The conditional
keyword in ListMap
essentially means: "Incorporate the ListMapIndex
mixin if-and-only-if my Key
type is an immutable Hashable
." Pause for a moment to think about what is happening here: It is as if the class is being automatically altered based on the type of Key
that the ListMap
is instantiated with!
There is one more way to specify a building block, which is the delegates
clause, seen here in the const ChickenOrEggMapping
class, shown here in full:
const ChickenOrEggMapping<Serializable>(function Mapping<Serializable>() egg)
delegates Mapping<Serializable>(chicken) {
private function Mapping<Serializable>() egg;
private @Lazy Mapping<Serializable> chicken.calc() {
return egg();
}
}
The "egg" function is provided as part of the constructor, and then the "chicken" is lazily created the first time that it is needed -- by calling the "egg" function and caching the resulting "chicken". What the delegates
clause does, though, is incredible: It automatically redirects all of the calls coming into the ChickenOrEggMapping
, and it will send them to the newly hatched chicken
. In other words, the delegates
clause implements an entire interface by routing all of the interface methods to another object -- and it does all that in one simple and obvious line of code. By delegating all of the Mapping
calls to the chicken, the ChickenOrEggMapping
acts just like a chicken, even though it was constructed with only an egg. And by delegating to a @Lazy
property, the first such delegating call to come in will automatically cause the chicken to be hatched from that egg!
Enumerations
Enumerations, or enums, allow us to define named constants representing a fixed set of options. Their syntax resembles Java's enum
. For example:
enum Event {Created, Modified, Deleted}
In this example, the Event
class is compiled as an @Abstract const
class, and the class object for the enumeration implements the Enumeration
interface, allowing easy programmatic access to the values of the enumeration, such as iterating through all of the enumeration's values:
for (Event event : Event.values) {
console.print($"event={event}");
}
Each enumeration value is a singleton const
class that extends the enumeration class and implements the Enum
interface; in the example, Created
is a singleton const
that extends Event
. This allows for simple and obvious usage of enumerations, such as:
Event event = Created;
And:
if (event != Deleted) {
process(event);
}
We hope this guide has shown you how to define and work with interface
, mixin
and enum
class keywords in Ecstasy for more idiomatic and sophisticated program control.
EXPLORE & LEARN
Ready to learn more?
Check out our latest guides, blogs and tutorials!