Note: Most of this has been implemented, other than the &- operator, closures, and init/drop methods.

Many operators are implemented as method calls. For instance, x+3 is treated as if it were: x.`+`(3) A type need only implement the `+` method to make the + operator meaningful to values of that type. If a program tries to apply the + operator to a value whose type has not implemented the `+` method, a compiler error results.

Let's explore the implications across a number of operators.

Value Operator Methods

A type may support any of the arithmetic operators (+, -, *, / and %) by implementing them as methods. The integer and floating point types do this, of course, but really any type that effectively encodes numbers or number structures can do this. This might include: complex numbers, "big" integers, rationals, unit-based numbers, vectors or matrices.

On the previous page, the Point type example took advantage of this by implementing the subtraction method:

fn `-`(pt2) Point:
	Point[pt2.x - x, pt2.y - y]

Note: Whenever a name includes non-alphanumeric (or underscore) characters, it must be enclosed in backticks. This is why the method name is shown as `-`.

With this method implemented, one may simply subtract two Points in an expression: pt1 - pt2.

Unary negation may also be implemented using the `-`. The difference between the negation method and the subtraction method is that the former needs only the self parameter:

fn `-`() Point:
  Point[-x, -y]

Logic and Shift Operator Methods

Similarly, a type may support any of the logic and shift operators (!, &, ^, ~, << and >>) by implementing them as methods.

Comparison Operator Methods

To support operator-based comparison of two values, implement the appropriate comparison operator(s) (==, <, <=, >, or >=) as methods. It is not necessary to implement the inequivalence operator (!=), as this is supported automatically if `==` is implemented.

isTrue "truthy" Method

Conditional expressions depend on a boolean value of true or false. If a conditional expression returns a value of some type other than Bool, Cone will check whether the type implements the isTrue method. If so, it will use that method to convert a conditional expression's to true or false.

In effect, this is how Cone supports the notion of type-specific "truthy" values. For example:

Value Mutation Methods

Several operator methods may be implemented that change an existing mutable value in memory. When the value is held by some variable, the compiler effectively obtains a mutable reference to the variable, and calls the method using the mutable reference.

This can be illustrated with a simple example. Let's say that a type wants to override the simple assignment operator with a method that performs a deep (rather than the default shallow) copy of the value. The signature of the method will accept a mutable reference to the target:

fn `=`(self &mut, value) Self:

When assignment is performed for a value of this type (a = b), it will obtain a mutable borrowed reference to the lval 'a' and then call the method like this: &uni a.`=`(b).

Arithmetic, Logic, Bitwise Assignment Operators

A custom type may also support any of the arithmetic assignment operators (+=, -=, *=, /= and %=) by implementing them as methods.

If a type does not implement the arithmetic assignment operators, the compiler will attempt to implement them using the arithmetic methods. Thus, pt1 += pt2 would be treated as if it were: pt1 = pt1 + pt2.

Similarly, the logic and shift assignment operators may also be implemented as methods. If not, they will be handled in the same way as the arithmetic assignment operators.

Increment and Decrement Operators

A type may implement the increment or decrement operators (++ and --) as methods. When used as prefix operators, their names are `++` and `--`. When used as postfix operators, their names are `_++` and `_--`.

Computed Properties

For public fields, there is often no reason to implement getters and setters to offer direct access to their values, because it is faster to access the fields directly. However, if it would be unsafe to offer direct access to certain fields, in order to preserve specific data invariants, one should make them private and then potentially offer appropriate getter and setter methods that preserve the invariants.

Implementing a getter and setter is easy:

struct Counter:
	_count u32 = 0

  // a getter
  fn counter() u32:
    _count

  // a setter (notice the use of a mutable reference, since the value will be changed)
  fn count(self &mut, cnt u32):
    _count = cnt

And here we use them:

mut x = Counter[]
x.counter // 0
x.counter = 10 // uses the "setter" to set the value
x.counter // 10

Since we can omit the parentheses when using a getter or setter, their use looks just like access to a public field. This makes it possible to start off with a type that makes a field public, and then later changes it to private because of an invariant. By implementing getters and setters instead, the API can be preserved even as the implementation evolves.

Collection Operators

By wrapping an array (or array reference) within a struct, one can create all sorts of collections: indexed collections like vectors and dictionaries, as well as stacks, queues, sets and more. The difference between them is largely determined by which methods are implemented for working with various elements of the collection. Most collections will typically implement the len method to find how many elements are currently in the collection, and iter to enable iteration through the collection's elements.

A couple of operators make it easier to work with collections: [] for indexed access and <- to add or remove an element. Each collection type implements methods that determine how these operators work.

[] Indexed Access Operator

As we saw with arrays, the [] operator is used to access a specific collection element according to its index. Implementing the [] method as both a getter and setter makes indexed access possible for other kinds of indexed collections. The index need not even be an unsigned integer, as it is for an array. Dictionaries, for example, often use strings as the index.

<- Append Operator

Some types may implement one or more overloaded methods for "<-", to "append" values to a receiver. This can be useful for a variety of purposes:

It is not necessary that all values appended to the receiver be data that is accumulated by the receiver. Some values could be understood to be instructional directives that alter the behavior of the receiver on the fly. For example, a directive could offer formatting, rendering or structuring instructions.

As a convenience, if a tuple appears after the append operator, this is transformed into multiple distinct append statements:

print <- "Hello ", user   // equivalent to: print <- "Hello "; print <- user

() "Function call" Operator

It is actually possible to "call" an object as if it were a function. Doing so will actually invoke the () method for the object.

Closures as anonymous structs

A closure is really just syntactic sugar for an anonymous struct that implements the () method. The closure |n i32 {cnt = 0}| cnt += n is roughly equivalent to:

struct Cntstr:
  _cnt = 0
  fn `()`(self &mut, n i32):
    _cnt += n

mut counter = Cntstr[]
&mut counter(1)        // 1
&mut counter(2)        // 3

As this example demonstrates, the closure itself is defined by the Cntstr struct. The closure's state is define by its field(s). The functional logic is captured within a single method named ().

Any explicitly callable struct is effectively also a closure.

Initializers and Finalizers

A type may define one or more initializers. Initializers offer an alternative to the type constructor to create a new value of that type. In contrast to a type constructor, which uses square brackets, an initializer uses parentheses:

imm pt = Point(1.)

The default name for initializers is init. An initializer may, however, use some other name. In particuar, an initializer named clone is automatically used (when defined) whenever a copy of a value needs to be made due to assignment, function call parameters, etc.

A type may also define a single finalizer called final. This is automatically invoked immediately before any value of that type is destroyed, and its memory space reclaimed.

On a later page, detailed advice is given on how to define initializers and finalizers.

_