Traits and Interfaces

Like other object-oriented languages, Pony has subtyping. That is, some types serve as categories that other types can be members of.

There are two kinds of subtyping in programming languages: nominal and structural. They're subtly different, and most programming languages only have one or the other. Pony has both!

Nominal subtyping

This kind of subtyping is called nominal because it is all about names.

If you've done object-oriented programming before, you may have seen a lot of discussion about single inheritance, multiple inheritance, mixins, traits, and similar concepts. These are all examples of nominal subtyping.

The core idea is that you have a type that declares it has a relationship to some category type. In Java, for example, a class (a concrete type) can implement an interface (a category type). In Java, this means the class is now in the category that the interface represents. The compiler will check that the class actually provides everything it needs to.

Traits: nominal subtyping

Pony has nominal subtyping, using traits. A trait looks a bit like a class, but it uses the keyword trait and it can't have any fields.

trait Named
  fun name(): String => "Bob"

class Bob is Named

Here, we have a trait Named that has a single function name that returns a String. It also provides a default implementation of name that returns the string literal "Bob".

We also have a class Bob that says it is Named. This means Bob is in the Named category. In Pony, we say Bob provides Named, or sometimes simply Bob is Named.

Since Bob doesn't have its own name function, it uses the one from the trait. If the trait's function didn't have a default implementation, the compiler would complain that Bob had no implementation of name.

trait Named
  fun name(): String => "Bob"

trait Bald
  fun hair(): Bool => false

 class Bob is (Named & Bald)

It is possible for a class to have relationships with multiple categories. In the above example, the class Bob provides both Named and Bald.

trait Named
  fun name(): String => "Bob"

trait Bald is Named
  fun hair(): Bool => false

 class Bob is Bald

It is also possible to combine categories together. In the example above, all Bald classes are automatically Named. Consequently, the Bob class has access to both hair() and name() default implementation of their respective trait. One can think of the Baldcategory to be more specific than the Named one.

class Larry
  fun name(): String => "Larry"

Here, we have a class Larry that has a name function with the same signature. But Larry does not provide Named!

Wait, why not? Because Larry doesn't say it is Named. Remember, traits are nominal: a type that wants to provide a trait has to explicitly declare that it does. And Larry doesn't.

Structural subtyping

There's another kind of subtyping, where the name doesn't matter. It's called structural subtyping, which means that it's all about how a type is built, and nothing to do with names.

A concrete type is a member of a structural category if it happens to have all the needed elements, no matter what it happens to be called.

If you've used Go, you'll recognise that Go interfaces are structural types.

Interfaces: structural subtyping

Pony has structural subtyping too, using interfaces. Interfaces look like traits, but they use the keyword interface.

interface HasName
  fun name(): String

Here, HasName looks a lot like Named, except it's an interface instead of a trait. This means both Bob and Larry provide HasName! The programmers that wrote Bob and Larry don't even have to be aware that HasName exists.

Pony interfaces can have functions with default implementations as well. A type will only pick those up if it explicitly declares that it is that interface.

Should I use traits or interfaces in my own code? Both! Interfaces are more flexible, so if you're not sure what you want, use an interface. But traits are a powerful tool as well: they stop accidental subtyping.

results matching ""

    No results matching ""