LocalSolver logo
is now
Hexaly logo

We're excited to share that we are moving forward. We're leaving behind the LocalSolver brand and transitioning to our new identity: Hexaly. This represents a leap forward in our mission to enable every organization to make better decisions faster when faced with operational and strategic challenges.


Classes

Classes are used to group together data and functions. Once a class has been declared, you can instantiate it to create objects, each with its own set of data (called fields), on which you can call the declared functions to modify their state (called methods).

LSP supports simple and virtual inheritance, constructors, calls to parent methods and final classes. Unlike other languages, there is no notion of visibility in LSP, and all members are public. Any code can therefore access the fields of any class.

Class declaration

A class is simply declared as follows:

class Duck {
    name;
    weight;

    quack() {
        println("Quack! Quack!");
    }
}

Class names in LSP follow the PascalCase naming conventions and start with a capital letter. Methods and fields form the members of the class. They follow the snakeCase convention (names start with a lowercase letter).

Fields and methods can be declared in any order within the class. It is not mandatory to declare all methods or fields together, although conventionally fields are declared at the top of the class, followed by the optional constructor and then the methods. The order in which members are declared is generally not important, except for the declaration of fields, which are initialized in the same order as their declaration order.

Member names must be unique throughout the class. It is therefore not possible to declare a method with the same name as a field, or vice versa.

Once a class has been declared, it is not possible to dynamically add new methods or fields to the class. This is a major difference from other dynamic languages such as Python or JavaScript. From this point of view, LSP is much more similar to strongly typed languages like C# or Java. As a result, the following code will generate an error at runtime:

douglass = new Duck();
// Will throw an error at runtime since the field 'color' does not exist on the class Duck
douglass.color = "green";

Object instanciation

To instantiate objects of a class, use the special operator new followed by the class name:

myObject = new MyClass();

If you have declared a constructor, you can also pass arguments to this constructor, just like any other function call:

myObject2 = new MyClass2(arg1, arg2, arg3);

The number of arguments passed must be exactly equal to the number of arguments declared in the constructor. LSP does not currently support the notion of optional arguments.

Methods

A method is a function attached to an object that can be used to view or modify its state. A method must be declared and defined in its enclosing class. To do this, simply write the name of the method followed by the list of arguments in brackets, as for a classic function. Note, however, that unlike a module function, the declaration is not prefixed with the keyword function.

The object’s state can be accessed from the method using the special keyword this followed by a dot and the name of the member to be used.

Examples of methods and their declarations are given in the following sections.

Constructor

The constructor is a special method that is called automatically when a new object is instantiated. The constructor behaves like a function, except that it is declared with the special constructor keyword and can never be called directly. The constructor can take parameters which are generally used to initialize the object’s fields. These parameters must be specified when calling the new operator:

class Duck {
    name;
    weight;

    constructor(name, weight) {
        this.name = name;
        this.weight = weight;
    }
    ...
}

Fields

Each object has its own instantiation of fields. To declare a field in a class, simply enter its name on a single line terminated by a ‘;’. You can also initialize the field using the equals sign, followed by any expression:

class Duck {
  // Simple field
  name;
  // Field followed by an initializer
  weight = 0.0;
}

To access a field from a function or method outside the object, simply use the object name followed by a . and the field name. Unlike other languages, there is no notion of visibility in LSP: all fields are public. This means that any function, class or module can access all the fields of all your objects:

douglass = new Duck();
println("My duck weights: " + douglass.weight);

When you need to access a field from a method of its own class, the object name is replaced by the keyword this:

class Duck {
    ...

    quack() {
        println(this.name + " is quacking: Quack! Quack!");
    }
}

Initialization

The default value of a field that is not explicitly initialized is nil. You can initialize a field explicitly in several ways: either from the constructor or directly when declaring the field as described above.

Note that initialization at field declaration is executed before the constructor. In the following example, the code will display the value 3.5 and not 3:

class Duck {
    name;
    weight = 3;

    constructor(name, weight) {
      this.name = name;
      this.weight = weight;
    }
}

douglass = new Duck("Douglass", 3.5);
println(douglass.weight);

Static fields

If you want the value of a field to be identical for all instances of a class, you can declare the field as “static”. Static fields behave in much the same way as global variables in modules, except that they are attached to a class and can be initialized to a value, even if no object of the class has been instantiated.

To declare a static field, simply use the keyword static followed by the field name, which can be optionnaly completed by an initializer introduced by the = symbol:

class Duck {
    ..
    static nbWings = 2;
}

To access the field, simply write the class name followed by a . and the field name. Note that static fields can only be accessed from the type, and cannot be accessed from object instances:

// Field access from the name of the class
println("Ducks have " + Duck.nbWings + " wings");

// The following code will throw an error: nbWings is a static field and
// cannot be accessed from an instance
duck = new Duck();
println(duck.nbWings); // Throw an error

Note also that a static field is not necessarily constant or immutable, and its value can be modified like any other global variable.

Inheritance

LSP supports simple inheritance. This means that a class can inherit from no more than one other class. The syntax to declare a derived class looks like this:

class Wigeon extends Duck {
    ...
}

When you declare Wigeon, the base class must already be defined. In other words, the base class must either have been previously imported using a use statement like use Duck from animals or the base class must have been previously declared in the current module. This rule is applied recursively if the base class itself is derived from some other class.

Derived classes may override methods of their base classes. Only method inheritance is supported. Consequently, it is not possible to redeclare a field whose name is already used in the parent class. When you wish to override a method in the child class, the new declaration must be prefixed by the keyword override like this:

class Duck {
    quack() {
        println("Duck is quacking");
    }
}

class Wigeon extends Duck {
    override quack() {
        println("Wii-wuuuuu !");
    }
}

Inheritance in LSP is said to be virtual in the sense that a base class that calls another method defined in the same base class may call a method of a derived class that overrides it.

Calling a base method

When one method overloads another, the base method is completely replaced by the new one. However, it is possible to call the old definition from the new method using the super keyword followed by the name of the function. Apart from the super prefix, this syntax behaves like any other function call.

If we take the example given above and modify it slightly, the overloaded quack method now displays two lines:

class Wigeon extends Duck {
    override quack() {
        println("Wigeon quacking is different:");
        super.doSomething();
    }
}

Constructor inheritance

It is not mandatory to redeclare a constructor in the child class. If you don’t declare one, a default constructor will be automatically created for you. It will have the same number of arguments as the parent constructor and will have the same behavior.

If you nevertheless decide to declare a constructor in the child class, a few specific rules apply:

  • Do not use the override keyword. Unlike methods, it is not necessary to add the override prefix to constructors of child classes.

  • A call to the parent constructor is mandatory. Unlike methods where calling the parent method is optional, you have to call the parent constructor. This is done using the following syntax: super(arg1, arg2, ...) with arg1, arg2, ... the arguments expected by the parent constructor. Calling the parent constructor is mandatory, even if it has no arguments.

  • The call to the parent constructor must be the first instruction of your child constructor. The following code will generate an error:

    class MyExtendedClass extends MyBaseClass {
        constructor(arg) {
            // Super is not the first instruction of the child constructor
            if (arg % 2 == 0) super("even");
            else super("odd");
        }
    
    }
    
  • You can’t use the this keyword before the parent constructor is called. The following code will generate an error:

    class MyExtendedClass extends MyBaseClass {
        constructor() {
            // Cannot use the ``this`` keyword before calling the parent constructor
            super(this.baseField ? "optionA" : "optionB");
        }
    }
    

Final class

To prevent a class from being extended, it can be sealed by prefixing its declaration with the keyword final. Any attempt to extend a final class will generate an error:

final class MySealedClass {

}

// Will result in a compile-time error
// "Cannot extend the class 'MySealedClass' which is marked final"
class MyExtendedClass extends MySealedClass {

}