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.

Enumerations

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:

  1. primitive Red
  2. primitive Blue
  3. primitive Green
  4. 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 is.

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,

  1. primitive Red fun apply(): U32 => 0xFF0000FF
  2. primitive Green fun apply(): U32 => 0x00FF00FF
  3. primitive Blue fun apply(): U32 => 0x0000FFFF
  4. type Colour is (Red | Blue | Green)

or namespace them like this

  1. primitive Colours
  2. fun red(): U32 => 0xFF0000FF
  3. fun green(): U32 => 0x00FF00FF

You might also want to iterate over the enum like this to print its name for debugging purposes

  1. primitive ColourList
  2. fun apply(): Array[Colour] =>
  3. [Red; Green; Blue]
  4. for colour in ColourList().values() do
  5. end

Complex types

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:

  1. interface HasName
  2. fun name(): String
  3. interface HasAge
  4. fun age(): U32
  5. interface HasFeelings
  6. fun feeling(): String
  7. type Person is (HasName & HasAge & HasFeelings)

This use of complex types applies to traits, not just interfaces:

  1. trait HasName
  2. fun name(): String => "Bob"
  3. trait HasAge
  4. fun age(): U32 => 42
  5. trait HasFeelings
  6. fun feeling(): String => "Great!"
  7. 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 HasName, HasAge and HasFeelings.

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:

  1. 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:

  1. HashSet[A, HashIs[A!]]

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 Map:

  1. 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 HashMap.

Other stuff

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 is.

In fact, that’s exactly what the compiler does.