Just like other object-oriented languages, Pony has classes. A class is declared with the keyword
class, and it has to have a name that starts with a capital letter, like this:
Do all types start with a capital letter? Yes! And nothing else starts with a capital letter. So when you see a name in Pony code, you will instantly know whether it’s a type or not.
What goes in a class?¶
A class is composed of:
These are just like fields in C structs or fields in classes in C++, C#, Java, Python, Ruby, or basically any language, really. There are three kinds of fields:
embed fields. A
var field can be assigned to over and over again, but a
let field is assigned to in the constructor and never again. Embed fields will be covered in more detail in the documentation on variables.
class Wombat let name: String var _hunger_level: U64
Wombat has a
name, which is a
String, and a
_hunger_level, which is a
U64 (an unsigned 64-bit integer).
What does the leading underscore mean? It means something is private. A private field can only be accessed by code in the same type. A private constructor, function, or behaviour can only be accessed by code in the same package. We’ll talk more about packages later.
Pony constructors have names. Other than that, they are just like constructors in other languages. They can have parameters, and they always return a new instance of the type. Since they have names, you can have more than one constructor for a type.
Constructors are introduced with the new keyword.
class Wombat let name: String var _hunger_level: U64 new create(name': String) => name = name' _hunger_level = 0 new hungry(name': String, hunger': U64) => name = name' _hunger_level = hunger'
Here, we have two constructors, one that creates a
Wombat that isn’t hungry, and another that creates a
Wombat that might be hungry or might not. Unlike some other languages that differentiate between constructors with method overloading, Pony won’t presume to know which alternate constructor to invoke based on the arity and type of your arguments. To choose a constructor, invoke it like a method with the
let defaultWombat = Wombat("Fantastibat") // Invokes the create method by default let hungryWombat = Wombat.hungry("Nomsbat", 12) // Invokes the hungry method
What’s with the single quote thing, i.e. name’? You can use single quotes in parameter and local variable names. In mathematics, it’s called a prime, and it’s used to say “another one of these, but not the same one”. Basically, it’s just convenient.
Every constructor has to set every field in an object. If it doesn’t, the compiler will give you an error. Since there is no
null in Pony, we can’t do what Java, C# and many other languages do and just assign either
null or zero to every field before the constructor runs, and since we don’t want random crashes, we don’t leave fields undefined (unlike C or C++).
Sometimes it’s convenient to set a field the same way for all constructors.
class Wombat let name: String var _hunger_level: U64 var _thirst_level: U64 = 1 new create(name': String) => name = name' _hunger_level = 0 new hungry(name': String, hunger': U64) => name = name' _hunger_level = hunger'
Wombat begins a little bit thirsty, regardless of which constructor is called.
Zero Argument Constructors¶
class Hawk var _hunger_level: U64 = 0 class Owl var _hunger_level: U64 new create() => _hunger_level = 42
Here we have two classes, because the
Hawk class defines no constructors, a default constructor with zero arguments called
create is generated. The
Owl defines its own constructor that sets the
When constructing instances of classes that have zero-argument constructors, they can be constructed with just the class name:
class Forest let _owl: Owl = Owl let _hawk: Hawk = Hawk
This is explained later, in more detail in the sugar section.
Functions in Pony are like methods in Java, C#, C++, Ruby, Python, or pretty much any other object oriented language. They are introduced with the keyword
fun. They can have parameters like constructors do, and they can also have a result type (if no result type is given, it defaults to
class Wombat let name: String var _hunger_level: U64 var _thirst_level: U64 = 1 new create(name': String) => name = name' _hunger_level = 0 new hungry(name': String, hunger': U64) => name = name' _hunger_level = hunger' fun hunger(): U64 => _hunger_level fun ref set_hunger(to: U64 = 0): U64 => _hunger_level = to
The first function,
hunger, is pretty straight forward. It has a result type of
U64, and it returns
_hunger_level, which is a
U64. The only thing a bit different here is that no
return keyword is used. This is because the result of a function is the result of the last expression in the function, in this case, the value of
Is there a
return keyword in Pony? Yes. It’s used to return “early” from a function, i.e. to return something right away and not keep running until the last expression.
The second function,
set_hunger, introduces a bunch of new concepts all at once. Let’s go through them one by one.
refkeyword right after
This is a reference capability. In this case, it means the receiver, i.e. the object on which the
set_hunger function is being called, has to be a
ref type. A
ref type is a reference type, meaning that the object is mutable. We need this because we are writing a new value to the
What’s the receiver reference capability of the
hunger method? The default receiver reference capability if none is specified is
box, which means “I need to be able to read from this, but I won’t write to it”.
What would happen if we left the
ref keyword off the
set_hunger method? The compiler would give you an error. It would see you were trying to modify a field and complain about it.
= 0after the parameter
This is a default argument. It means that if you don’t include that argument at the call site, you will get the default argument. In this case,
to will be zero if you don’t specify it.
- What does the function return?
It returns the old value of
Wait, seriously? The old value? Yes. In Pony, assignment is an expression rather than a statement. That means it has a result. This is true of a lot of languages, but they tend to return the new value. In other words, given
a = b, in most languages, the value of that is the value of
b. But in Pony, the value of that is the old value of
…why? It’s called a “destructive read”, and it lets you do awesome things with a capabilities-secure type system. We’ll talk about that later. For now, we’ll just mention that you can also use it to implement a swap operation. In most languages, to swap the values of
b you need to do something like:
var temp = a a = b b = temp
In Pony, you can just do:
a = b = a
Finalisers are special functions. They are named
_final, take no parameters and have a receiver reference capability of
box. In other words, the definition of a finaliser must be
The finaliser of an object is called before the object is collected by the GC. Functions may still be called on an object after its finalisation, but only from within another finaliser. Messages cannot be sent from within a finaliser.
Finalisers are usually used to clean up resources allocated in C code, like file handles, network sockets, etc.
What about inheritance?¶
In some object-oriented languages, a type can inherit from another type, like how in Java something can extend something else. Pony doesn’t do that. Instead, Pony prefers composition to inheritance. In other words, instead of getting code reuse by saying something is something else, you get it by saying something has something else.
On the other hand, Pony has a powerful trait system (similar to Java 8 interfaces that can have default implementations) and a powerful interface system (similar to Go interfaces, i.e. structurally typed).
We’ll talk about all that stuff in detail later.
All names in Pony, such as type names, method names, and variable names may contain only ASCII characters.
In fact all elements of Pony code are required to be ASCII, except string literals, which happily accept any kind of bytes directly from the source file, be it UTF-8 encoded or ISO-8859-2 and represent them in their encoded form.
A Pony type, whether it’s a class, actor, trait, interface, primitive, or type alias, must start with an uppercase letter. After an underscore for private or special methods (behaviors, constructors, and functions), any method or variable, including parameters and fields, must start with a lowercase letter. In all cases underscores in a row or at the end of a name are not allowed, but otherwise, any combination of letters and numbers is legal.
In fact, numbers may use single underscores inside as a separator too!
Only variable names can end in primes (