Inheritance¶
Problem¶
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.
Solution¶
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 true
.
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.
Discussion¶
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.
For example:
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 do_you_want_free_candy
. 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.