User defined types - Palamecia/mint GitHub Wiki

User-defined types are custom types that allow for the description of custom values, data structures, and behaviors. There are two kinds of user-defined types:

Once defined, these types can be used like other built-in types.

Note

If a type is defined within a control structure, the type will be effectively defined only if the definition script is executed.

Enumerations

Enumerations are the simplest type definitions, providing only a list of global constant values.

The definition of an enumeration is introduced by the enum keyword followed by a name corresponding to the type name. The definition opens a block of constant definitions.

Example:

enum MyEnum {
}

Constants are defined within the block using a list of symbol names. The = operator can follow the name to set the value of the constant. The value must be a number. If no value is given, a value is automatically set by adding one to the previous value inserted. If no previous value was inserted, 0 is used.

Example:

enum MyEnum {
    first       // gives 0
    second = 5  // gives 5
    third       // gives 6
}

Tip

The is operator can be used to check if the value of a variable is a constant of an enumeration.

Classes

Classes describe a list of member symbols that will be associated with each instance of the class. Each member has a visibility and a value.

A class definition is introduced by the class keyword followed by a name corresponding to the type name. The : operator can optionally be added to introduce a list of base classes. The definition opens a block of member definitions.

Example:

class MyClass {
}

Members visibility

Members visibility modifiers can be added before access modifiers. They change the accessibility of the member depending on the context. By default, each member can be accessed from anywhere.

The following visibility modifiers can be added to any member:

  • + (public): The member can be accessed from anywhere. This is the default.
  • # (protected): The member can only be accessed from inside the class or from a derived or base class.
  • - (private): The member can only be accessed from inside the class.
  • ~ (package): The member can only be accessed from the same package.

Example:

class MyClass {
    publicMember
    + otherPublicMember
    + const constPublicMember
    # protectedMember
    - privateMember
    ~ packageMember
    - @privateGlobalMember
}

Important

Members with a global access modifier initialized to none have a special behavior. They can be accessed from anywhere regardless of the visibility modifier used until their value is initialized.

Members value

Each member of a class has a value. The = operator can follow the member name to initialize this value. If no value is given, the none value is used.

Example:

class MyClass {
    member1 = 0
    member2 = 'text'
    member3 = false
}

The member definition block has no context. Therefore, no variable or user-defined type can be used to initialize a member. The null value can then be used as a placeholder before the initialization of the member in the constructor.

Note

It is recommended to use the null value for members that will be initialized later, like in a constructor or in an update method, and the none value for optional members like callbacks or handlers.

Values from built-in type

Any built-in type can be used as a member value. However, as the member definition block has no context, some special conditions apply to some types:

User defined member types

Classes can also contain other user-defined types as global members. Those types can use the visibility mechanism and are introduced by the corresponding keyword.

Example:

class MyClass {
    enum MyEnum {
    }

    class MyChildClass {
    }

    - class MyPrivateClass {
    }
}

Methods

Members using the function type are called methods. Non-global methods must have at least one parameter. This first parameter will be used to store the object instance.

Note

By convention, the first parameter of a method is named self, but any name can be used.

As a function value can have several associated blocks, the += operator can be used to add a block to a function member.

Example:

class MyClass {
    method = def (self) {}
    method += def (self, value) {}
}

The def keyword can also be placed before the member name to create a method. In this case, the method will be automatically created or extended.

Example:

class MyClass {
    def method(self) {}
    def method(self, value) {}
}

Constructor and destructor

Classes can have two special members: new and delete.

The new member is called the constructor. When a new instance of the class is created, this member is called. The returned value is then used as the new instance.

The call parameter list of the constructor starts with the instance of the object being created. This instance is only initialized with the values associated with the class members. Additional parameters can follow and must then be passed when the class is instantiated.

Example:

class MyClass {
    def new(self, value) {
        self.value = value
        return self
    }

    - value = null
}

Note

If the constructor is not defined, the class instantiation gives an object only initialized with the values associated with the class members.

The delete member is called the destructor. When an instance is destroyed, this member is called. The returned value is ignored.

The destructor can take only one parameter corresponding to the instance of the object being destroyed. Some final operations can then be processed on the object.

Example:

class MyClass {
    def new(self) {
        if MyClass.user is null {
            MyClass.user = self
            return self
        }
    }

    def delete(self) {
        if MyClass.user is self {
            MyClass.user = null
        }
    }

    - @user = null
}

Operator overloading

Some operators can be overloaded by a class. To override the default behavior of an operator, a special member can be added. These members are introduced by the def keyword followed by the name of the operator to overload. They use a call parameter list with an amount of parameters depending on the overloaded operator.

Example:

def MyClass {
    def +(self) {}
    def +(self, value) {}

    def [](self, key) {}
    def []=(self, key, value) {}

    def ()(self, a, b, c) {}
}

Inheritance

After the class name, the : operator can be added to insert a base class list. The base class list contains the names of the classes to extend separated by ,.

Example:

class MyBase {
}

class MyOtherBase {
}

class MyClass : MyBase, MyOtherBase {
}

Note

A class can also extend an enumeration, but built-in types cannot be extended.

Each non-global member of the extended classes will then be copied to the newly created class with their values and modifiers, except for members already defined in the class.

Example:

class MyBase {
    member1 = 1
    member2 = 2
}

class MyClass : MyBase {
    member1 = 5  // member1 gives 5
                 // member2 gives 2
}

Important

When a class extends several classes that contain members in common, those members must be redefined in the new class to resolve them.

Members can also be defined with the final or override modifier to change the behavior when a member is redefined in the child class.

A member defined with the final modifier cannot be redefined in child classes.

Example:

class MyBase {
    final member = 1
}

class MyClass : MyBase {
    member = 5  // error: member 'member' overrides a final member of 'MyBase' for class 'MyClass'
}

A member defined with the override modifier must be already defined in at least one base class.

Example:

class MyBase {
    member1 = 1
}

class MyClass : MyBase {
    override member2 = 5  // error: member 'member2' is marked override but does not override a member for class 'MyClass'
}

A method defined with the override modifier also requires that the child class redefine each signature of the base class's member.

Example:

class MyBase {
    def method(self) {}
    def method(self, value) {}
}

class MyClass : MyBase {
    override def method(self) {}
    // error: member 'method' is marked override but is missing signature '()'(2) for class 'MyClass'
}

Base class members' default value is accessible from derived classes. To access a base class member, the base class name is used followed by the . operator and the member name. This behavior allows to use base classes' constructor/destructor, method implementation, and default values.

Important

To use a base class method implementation, the object instance must be explicitly passed as the first parameter.

Example:

class MyBase {
    def new(self) {}
    def method(self) {}

    # member = false
}

class MyOtherBase {
    def new(self) {}
    def method(self) {}

    # member = true
}

class MyClass : MyBase, MyOtherBase {
    def new(self) {
        if self = MyBase.new(self) {
            if self = MyOtherBase.new(self) {
                self.member = MyBase.member
                return self
            }
        }
    }

    def method(self) {
        return MyBase.method(self) + MyOtherBase.method(self)
    }

    - member = false
}

Note

Base class methods can also be redefined in the constructor of the derived class. The method override will then be on the instance and not on the whole class.

Example:

class MyBase {
    # def method(self) {
        return false
    }
}

class MyClass : MyBase {
    def new(self, override) {
        if override {
            self.method = def (self) {
                return true
            }
        }
        return self
    }
}

Instantiation

A user-defined type instance is created with the call operator on the type. The operator gives an object containing a copy of each non-global member of the type initialized by the constructor if any.

The amount of parameters of the operator depends on the constructor, but the first parameter must not be passed. If the type does not have a constructor, the operator takes no parameters.

Example:

class MyClass {
    def new(self, value) {}
}

myObject = MyClass(5)

Note

The instantiation of an enumeration is meaningless because those types can only have global members.

To access a member of the object, the . operator is used. If the member is a method and the call operator is used on the member, the object will automatically be passed as the first parameter.

Example:

class MyClass {
    def method(self, value) {}
    member = 5
}

myObject = MyClass()
myObject.member    // gives 5
myObject.method(5) // call MyClass.method(myObject, 5)

Note

To call a member function without using the object instance as the first parameter, the ( and ) operators can be used around the function access.

Example:

(myObject.func)() // call the function with no parameters

Global members can be accessed in the same way, but they can also be directly accessed from the class.

Example:

class MyClass {
    @member = 5
}

myObject = MyClass()
myObject.member   // gives 5
MyClass.member    // gives 5

Important

The object instance is not passed to the parameter list when the called method is global.

A user-defined type can also be stored in a variable. This allows for the use of types as parameters or to create type aliases. To store the type, the type name can be used without the call operator.

Example:

@TheClass = MyClass      // TheClass is an alias of MyClass
createList(MyClass, 5)   // Call createList with MyClass as the first parameter

Copy

If a copy of an object is required, a new instance will be created without member initialization or a call to the constructor. The value of each member will then be copied from the object to the copy instance recursively. This mechanism keeps track of any copied instance created for each value, and the same instance will be used if the same value is used more than once.

If extra operations should be performed during the copy, a special member clone can be declared. If this attribute is added to a class, the standard copy mechanism is disabled, and the method should be called instead. This member will also disable the standard copy mechanism for any class that uses the class using inheritance or composition.

Example:

class MyClass {
    // Count
    @copyCount = 0
    - value = 0

    const def clone(const self) {
        MyClass.instanceCount++
        var other = MyClass()
        other.value = self.value
        return other
    }
}

Tip

The clone member can also be declared as a private member to disable the copy.

Note

The mint.type module provides a clone function that calls the clone method of the provided object and falls back to the standard copy mechanism if not defined.


« Function definition   Work with modules »
⚠️ **GitHub.com Fallback** ⚠️