Class

In Orange, a class is essentially a named tuple that is defined with optional methods, properties, and inheritance from a single base type.

Here's an example of a Dog class:

enum DogType {
    GoodDog,
    WeirdDog
}

extend DogType : Printable<DogType> {
    def toString() {
        return switch (this) {
            DogType.GoodDog: "good dog",
            DogType.WeirdDog: "weird dog"
        }
    }
}

class Dog {
    var name: string
    var age: uint
    var type: DogType

    def bark(): Console.Log("Woof. I am a ${age} year old dog named ${name} and I am a ${type}.")

    def Dog(name: string, age: uint, type: DogType) {
        this.name = name
        this.age = age
        this.type = type
    }

    def ~Dog() {
        Console.Log("Goodbye, human. Woof.")
    }
}

var weirdDog = Dog("Fido", 4, DogType.WeirdDog)
weirdDog.bark() // => Woof. I am a 4 year old dog named Fido and I am a weird dog.


// => Goodbye, human. Woof.

The method on Dog called Dog is a constructor; it is a method that is called upon creation of the class type with all the provided parameters. The ~Dog method is a descructor and is called when the object is out of scope (if it was created on the stack, or if deleted manually otherwise).

Modifying the receiver type of a method

Along with the standard privacy flags and static, Orange allows adding other kinds of flags to modify the type of method that will be compiled.

By default, all methods will recieve an lvalue reference (T&) allowing for modification of this and using it as any other normal object on the stack.

However, in some cases, it may be required or desired to use an rvalue instead. This can be achieved in one of two ways:

  1. Using the copied flag before the def keyword
  2. Adding the type attribute @Receiver(this) before the function definition.

Without the copied flag, the compiler automatically injects the @Reciever(this&) type attribute to all non-static methods.

The @Receiver type attribute is built-in to the compiler and only accepts either this or this& as its parameter, where this refers to a type, not an object.

Here's an example of using an rvalue on an exension to the int type:

extent int {
  // copied could be replaced with @Receiver(this) here
  copied def abs() {
    if (this < 0) {
      return -this 
    }

    return this 
  }
}

var num = 53 

-5.abs() // 5 
num.abs() // 53

Properties

Properties are methods that are used like normal object members. A property is defined with one or both of a getter and setter. If only a getter is defined, the property can not appear on the left-hand side of an assignment. If only a setter is defined, the property can not appear on the right-hand side of an assignment.

The various ways to deeclare a property are as follows:

property PropertyName -> optionalType {
    get { yield someExpression }
    set(value: someType) {
        // something
    }
}

property PropertyName {
    get: someExpression
    set: someExpression // type of value is inferred from type of someExpression
}

// in here, get/set is automatically generated. get will return someExpression
// and set will change someExpression.
property PropertyName: someExpression

// when readonly, no set is generated. If this was in the long-block format,
// defining a set would be a compilation error.
readonly property PropertyName: someExpression

Note that extend supports defining properties on the extended type.

Default Constructors

By default, every class has two constructors defined by default:

  1. The parameterless constructor
  2. A constructor that has one parameter for each public member

For example, here is a class and an explicit list of default constructors:

class Vector {
    var x: int
    var y: int

    // Default constructor defined here explicitly
    Vector(x: int, y: int) {
        this.x = x
        thix.y = y
    }

    // Default constructor defined here explicitly
    Vector() {
        this.x = int() // initialize using the default ctor of int
        this.y = int() // initialize using the default ctor of int
    }
}

These constructors allow you to create a vector by passing in the parameters by name: Vector(x: 5, y: 20).

Optional parameters and RequireOne

The previous example provided doesn't fully match the default behavior, however: default constructors for members actually have all parameters as optional.

class Vector {
    var x: int
    var y: int

    // Default constructor defined here explicitly
    @RequireOne
    Vector(x: int = int(), y: int = int()) {
        this.x = x
        thix.y = y
    }

    // Default constructor defined here explicitly
    Vector() {
        this.x = int() // initialize using the default ctor of int
        this.y = int() // initialize using the default ctor of int
    }
}

Now you can construct a vector with any number of its members, so both Vector(x: 5) and Vector(y: 20) are valid constructors, leaving the other members to their default values.

Normally, there's an ambiguity here: we have a default constructor that takes zero parameters and another constructor that can take zero parameters. If you construct a Vector using no arguments, which constructor do we use?

The @RequireOne attribute on the constructor specifies that that function can only be used when at least one argument is supplied. Through this the ambiguities are avoided and the proper constructor will always be called.

Inheritance

A class can implement a any number of interfaces or extend a single type.

When defining a class, after the class name, include a semicolon and include the list of interfaces and optional parent class. The parent class must be a class and not a builtin type like int or a tuple.

When inheritance of a parent type is in affect, one of the parent constructors must be explicitly called if it is not an implicit constructor. This must be done via calling the super keyword as a function in all constructors of the child class.

If both the parent and the child class have default constructors, this happens automatically.

Partial Class

A class marked as partial (partial class MyClassName) means that multiple definitions of the class (also marked with partial) can occur anywhere in the project and define new methods and members on the class.

results matching ""

    No results matching ""