A type alias is just a way to give a different name to a type. This may sound a bit silly: after all, types already have names! However, Pony can express some complicated types, and it can be convenient to have a short way to talk about them.
We’ll give a couple examples of using type aliases, just to get the feel of them.
One way to use type aliases is to express an enumeration. For example, imagine we want to say something must either be Red, Blue or Green. We could write something like this:
primitive Red primitive Blue primitive Green type Colour is (Red | Blue | Green)
There are two new concepts in there. The first is the type alias, introduced with the keyword
type. It just means that the name that comes after
type will be translated by the compiler to the type that comes after
The second new concept is the type that comes after
is. It’s not a single type! Instead, it’s a union type. You can read the
| symbol as or in this context, so the type is “Red or Blue or Green”.
A union type is a form of closed world type. That is, it says every type that can possibly be a member of it. In contrast, object-oriented subtyping is usually open world, e.g. in Java, an interface can be implemented by any number of classes.
You can also declare constants like in C or Go like this, making use of
which can be omitted during call (will be discussed further in Sugar),
primitive Red fun apply(): U32 => 0xFF0000FF primitive Green fun apply(): U32 => 0x00FF00FF primitive Blue fun apply(): U32 => 0x0000FFFF type Colour is (Red | Blue | Green)
or namespace them like this
primitive Colours fun red(): U32 => 0xFF0000FF fun green(): U32 => 0x00FF00FF
You might also want to iterate over the enumeration items like this to print their value for debugging purposes
primitive ColourList fun apply(): Array[Colour] => [Red; Green; Blue] for colour in ColourList().values() do env.out.print(colour().string()) end
If a type is complicated, it can be nice to give it a mnemonic name. For example, if we want to say that a type must implement more than one interface, we could say:
interface HasName fun name(): String interface HasAge fun age(): U32 interface HasFeelings fun feeling(): String type Person is (HasName & HasAge & HasFeelings)
This use of complex types applies to traits, not just interfaces:
trait HasName fun name(): String => "Bob" trait HasAge fun age(): U32 => 42 trait HasFeelings fun feeling(): String => "Great!" type Person is (HasName & HasAge & HasFeelings)
There’s another new concept here: the type has a
& in it. This is similar to the
| of a union type: it means this is an intersection type. That is, it’s something that must be all of
But the use of
type here is exactly the same as the enumeration example above, it’s just providing a name for a type that is otherwise a bit tedious to type out over and over.
Another example, this time from the standard library, is
SetIs. Here’s the actual definition:
type SetIs[A] is HashSet[A, HashIs[A!]]
Again there’s something new here. After the name
SetIs comes the name
A in square brackets. That’s because
SetIs is a generic type. That is, you can give a
SetIs another type as a parameter, to make specific kinds of set. If you’ve used Java or C#, this will be pretty familiar. If you’ve used C++, the equivalent concept is templates, but they work quite differently.
And again the use of
type just provides a more convenient way to refer to the type we’re aliasing:
That’s another generic type. It means a
SetIs is really a kind of
HashSet. Another concept has snuck in, which is
! types. This is a type that is the alias of another type. That’s tricky stuff that you only need when writing complex generic types, so we’ll leave it for later.
One more example, again from the standard library, is the
Map type that gets used a lot. It’s actually a type alias. Here’s the real definition of
type Map[K: (Hashable box & Comparable[K] box), V] is HashMap[K, V, HashEq[K]]
Unlike our previous example, the first type parameter,
K, has a type associated with it. This is a constraint, which means when you parameterise a
Map, the type you pass for
K must be a subtype of the constraint.
Also, notice that
box appears in the type. This is a reference capability. It means there is a certain class of operations we need to be able to do with a
K. We’ll cover this in more detail later.
Just like our other examples, all this really means is that
Map is really a kind of
Type aliases get used for a lot of things, but this gives you the general idea. Just remember that a type alias is always a convenience: you could replace every use of the type alias with the full type after the
In fact, that’s exactly what the compiler does.