Table of Contents

Classes and User-defined Objects

Classes define user-created object types. A class contains constructor code, fields, and methods.

User-defined objects behave like other Expr objects: they can be stored in variables, passed to functions, returned from functions, used as callback owners, and accessed with method/property syntax.


Class Definition

A class is declared with `class`, a name, a parameter list, and a body.

class ToolInfo(number, diameter)
{
    Number = number;
    Diameter = diameter;
 
    function Radius()
    {
        return Diameter / 2;
    }
}

The class name becomes a constructor in the current session.

tool = ToolInfo(3, 6.0);
print(tool.Number);      // 3
print(tool.Radius());    // 3

Class declarations are top-level declarations. They are not allowed inside `if`, `while`, `for`, or other statement blocks.


Constructor Body

The class body is also the constructor body.

When an object is created, Expr runs the non-function statements in the class body once for that new object.

class ProbeResult(x, y, z)
{
    X = x;
    Y = y;
    Z = z;
    HasError = false;
}
 
result = ProbeResult(10, 20, -1);

Assignments in the constructor create object fields.

Function declarations inside the class body do not run during construction. They define methods.


Constructor Parameters

Class constructor parameters are positional.

class Point(x, y)
{
    X = x;
    Y = y;
}
 
p = Point(10, 20);

The argument count must match the parameter count. Default arguments and keyword arguments are not supported.

Point(10);           // error
Point(10, 20, 30);   // error

Use empty parentheses when a class has no parameters:

class Counter()
{
    Value = 0;
}
 
c = Counter();

Fields

Fields are per-object values.

Inside a constructor or method, plain assignment can create or update a field:

class Counter(start)
{
    Value = start;
 
    function Inc()
    {
        Value = Value + 1;
        return Value;
    }
}

Fields can be read with property syntax:

c = Counter(5);
print(c.Value);      // 5

Inside a method, `this.field` explicitly reads a field from the current object:

class Counter(start)
{
    Value = start;
 
    function Read()
    {
        return this.Value;
    }
}

Direct property assignment is not supported:

c.Value = 10;        // error
this.Value = 10;     // error

To change a field from inside a constructor or method, assign the bare field name.


Local Variables and Fields

`set` creates a local variable. It does not create or update an object field.

class Counter(start)
{
    Value = start;
 
    function Test()
    {
        set Value = 100;
        return this.Value;      // still the object field
    }
}

Use `this.field` when a local variable or parameter has the same name as a field.

class ToolInfo(number)
{
    Number = number;
 
    function SetNumber(number)
    {
        Number = number;
        return this.Number;
    }
}

Compound assignment can update an object field when the target resolves to that field:

class Counter()
{
    Value = 0;
 
    function Inc()
    {
        Value += 1;
        return Value;
    }
}

Compound assignment through property syntax is not supported:

this.Value += 1;     // error

Methods

Methods are declared with `function` inside the class body.

class Accumulator()
{
    Total = 0;
 
    function Add(value)
    {
        Total += value;
        return Total;
    }
}
 
acc = Accumulator();
acc.Add(10);
acc.Add(5);

A method call uses the same syntax as built-in object method calls:

object.Method(arguments);

Methods can return any Expr value. Returning `this` is useful for chaining:

class Builder()
{
    Text = '';
 
    function Add(text)
    {
        Text += text;
        return this;
    }
 
    function Get()
    {
        return Text;
    }
}
 
s = Builder().Add('A').Add('B').Get();

Method overloading by argument count is not supported for user-defined class methods. Use different method names.


this

`this` is available inside class constructor code and class methods.

Use `this` to:

class Counter(start)
{
    Value = start;
 
    function Add(value)
    {
        Value += value;
        return this.Value;
    }
 
    function AddTwice(value)
    {
        return this.Add(value) + this.Add(value);
    }
}

Inside a method, a bare function-style call can resolve to a method on `this` before falling back to session functions and built-in functions.

class Counter(start)
{
    Value = start;
 
    function Add(value)
    {
        Value += value;
        return Value;
    }
 
    function AddTwice(value)
    {
        return Add(value) + Add(value);
    }
}

Use `this.Add(value)` when you want the method call to be explicit.


Method References and Callbacks

A method can be referenced without calling it.

class DialogScript()
{
    Count = 0;
 
    function OnClick()
    {
        Count += 1;
    }
 
    function Show()
    {
        btn = textbutton().text('Run').on_click(this.OnClick);
        window().title('Demo').size(200, 100).add(btn).show();
    }
}
 
script = DialogScript();
script.Show();

`this.OnClick` is a method reference. It can be passed to APIs that expect a callback.

A method reference is an object value. It can also be called explicitly with `call(…)`.

callback = script.OnClick;
callback.call();

Callback argument rules depend on the object or host API that invokes the callback.


Namespaced Classes

Class names can use namespaces.

class Math::Accumulator()
{
    Total = 0;
 
    function Add(value)
    {
        Total += value;
        return Total;
    }
}
 
acc = Math::Accumulator();
acc.Add(5);

Namespaced includes can also place included class definitions into a namespace. See Include system.


Class and Function Name Resolution

When a call name can refer to both a class constructor and a function, Expr resolves the class constructor first.

function Box()
{
    return 'function';
}
 
class Box()
{
    Value = 'class';
}
 
b = Box();           // creates a Box object

Re-defining a class name in the same session replaces the previous class definition.


Sessions

User-defined classes belong to the active Expr session.

A class defined in one named session is not visible in another named session unless it is defined there too.

class Box(value)
{
    Value = value;
}
 
b = Box(7);

Use the global `session` object to inspect current session classes:

print(session.list_classes());

See session and Execution model and sessions.


Object Assignment and Clone

Object variables hold object references.

class Person()
{
    Name = '';
 
    function SetName(name)
    {
        Name = name;
    }
}
 
p1 = Person();
p1.SetName('A');
 
p2 = p1;
p2.SetName('B');
 
print(p1.Name);      // B

Use `clone()` when you need an independent copy.

p3 = p1.clone();
p3.SetName('C');

User-defined objects are cloneable when their field values are cloneable. If the object graph contains a non-cloneable object, `clone()` raises an error.


Current Limitations

Expr classes currently do not support:

Previous: snake

Next: Include system