Skip to content

Latest commit

 

History

History
168 lines (106 loc) · 8.78 KB

classes.md

File metadata and controls

168 lines (106 loc) · 8.78 KB

Classes

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:

class Wombat

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:

  1. Fields.
  2. Constructors.
  3. Functions.

Fields

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: var, let, and 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

Here, a 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.

Constructors

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

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'

Here, every Wombat begins a little bit thirsty, regardless of which constructor is called.

Functions

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 None).

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

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.

  • The ref keyword right after fun

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 _hunger_level field.

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.

  • The = 0 after the parameter to

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

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

...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 a and b you need to do something like:

var temp = a
a = b
b = temp

In Pony, you can just do:

a = b = a

Finalisers

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 fun _final().

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.

Naming rules

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! But only valid variable names can end in primes.