You want to share code between classes (or actors), but Pony doesn’t allow you to inherit from other classes. What’s an enterprising programmer to do? Pony is object-oriented and object-orientation means inheritance via classes, right?
Pony believes in “composition over inheritance” so, inheritance as you might be familiar with it isn’t available. However, this doesn’t mean that you can’t share implementation across differing types. Enter default implementations on traits and interfaces.
Pony interfaces and traits can provide default implementations for their defined methods. For example, here’s the definition of
TimerNotify from the standard library:
interface TimerNotify """ Notifications for timer. """ fun ref apply(timer: Timer, count: U64): Bool => """ Called with the the number of times the timer has fired since this was last called. Usually, the value of `count` will be 1. If it is not 1, it means that the timer isn't firing on schedule. For example, if your timer is set to fire every 10 milliseconds, and `count` is 2, that means it has been between 20-29 milliseconds since the last time your timer fired. Non 1 values for a timer are rare and indicate a system under heavy load. Return true to reschedule the timer (if it has an interval), or false to cancel the timer (even if it has an interval). """ true fun ref cancel(timer: Timer) => """ Called if the timer is cancelled. This is also called if the notifier returns false from its `apply` method. """ None
Note that each of the methods defines a default implementation. In the case of
apply, that default implementation is to return
fun ref apply(timer: Timer, count: U64): Bool => true
Not the most exciting of logic, we grant you that, however, it can be very useful. By defining standard interfaces for shared functionality, you can share default implementations across implementers. It’s not quite inheritance, but it can still take you a long way.
There are a couple of important points to note about using default implementations as a means of code sharing.
All default implementations must be stateless¶
Traits and interfaces can’t have fields, so, your implementation will be limited to processing incoming data and returning a value.
The user must opt-in to using default implementations¶
Default implementations are limited to nominally typed objects. What this means is that, any time a class implements a
trait all default implementations will be picked up. If you are using an
interface instead of a
trait, then the default type won’t be picked up unless a class is specifically declared as implementing the interface.
interface CandyMachine fun do_you_want_free_candy(): Bool => "Who doesn't want free candy? Not me! Gimme, Gimme, Gimme!" true class ChocolateMachine is CandyMachine class CookieMachine fun do_you_want_free_candy(): Bool => false
In our above example,
ChocolateMachine declares that it is a
CandyMachine by using
is CandyMachine. By nominally typing itself as a
CandyMachine, it picks up the default implementation of
CookieMachine, on the other hand, is not nominally typed as a
CandyMachine and therefore doesn’t pick up the default implementation.
Default implementations can be great way to share implementations across classes. However, the limitations that requires them to be stateless can at times be very constricting. If you need “stateful default implementations”, check out the Mixin pattern.