1. Methods

Methods are functions associated to object instances. Methods are described inside a class definition. Information about function presented in chapter Functions are applicable to methods. The grammar of a method is presented in the following code block.

method := simple_method | template_method

simple_method := 'def' Identifier method_params ('->' type)? expression
template_method := 'def' ('if' expression)? Identifier templates  ('->' type)? expression

method_params := '(' ('mut')? 'self' (',' param_decl)* ')'


Methods are accessible using an object instance, of the dot binary operator .. Once accessed, a method can be called using a list of arguments separated by comas inside parentheses. The first parameter of a method is the object instance, and is the left operand of the dot operator, so it must not be repeated inside the parentheses.

import std::io

class A {
    let _a : i32;

    pub self (a : i32) with _a = a {}


    pub def foo (self, x : i32) -> i32 {
        println ("Foo ", self._a);
        x + self._a 
    }
}

def main () {
    let a = A::new (29);
    println (a.foo (13));
}


Results:

Foo 29
42


The access to the fields, and to the methods of the object instance inside the body of a method is made using the first parameter of the method, the variable self. Unlike some object oriented language, such as Java, C++, Scala, D, etc. self is never implicit, e.g. accessing to the field _a in the above example, cannot be made by just writing _a, but must be accessed by writing self._a. This is a conceptual choice, whose purpose is to improve code readability and sharing, by avoiding useless and preventable search for the source of the variables.

1.1. Privacy

Methods are protected by default. Meaning that only the class that have defined them, and its heir class have acces to them. The keyword pub and prv can be used to change the privacy of a method. A public method is accessible everywhere, using an object instance, and private methods are only accessible by the class that have defined them. Unlike protected methods, private methods are not accessible by heir classes. Privacy of methods is the same as privacy of fields.

import std::io

class A {

    pub self () {}

    pub def foo (self) {
        println ("Foo");
        self.bar ();
    }

    prv def bar (self) {
        println ("bar");
    }
}


def main () {
    let a = A::new ();
    a.foo ();
    a.bar ();
}


Because the method bar is private in the context of the main function, the compiler returns the following error. One can note, that the compiler tried to rewrite the expression into a uniform call syntax (i.e. bar (a)), but failed, because the function bar does not exists.

Error : undefined field bar for element &(main::A)
 --> main.yr:(21,3)
21  ┃     a.bar ();
    ╋      ^^^^
    ┃ Note : bar --> main.yr:(12,10) : (const self) => main::A::bar ()-> void is private within this contextNote : when using uniform function call syntax
    ┃ Error : undefined symbol bar
    ┃  --> main.yr:(21,4)
    ┃ 21  ┃     a.bar ();
    ┃     ╋       ^^^
    ┗━━━━━┻━ 


ymir1: fatal error: 
compilation terminated.

1.2. Method mutability

The mutability of the object instance must be defined in the prototype of the method. By default the object instance refered to by self is immutable, meaning that the instance cannot be modified.

class A {

    let mut _x : i32;

    pub self (x : i32) with _x = x {}

    pub def setX (self, x : i32) {
        self._x = x;
    }    

}


Error:

Error : when validating main::A
 --> main.yr:(1,7)
 1  ┃ class A {
    ╋       ^
    ┃ Error : left operand of type i32 is immutable
    ┃  --> main.yr:(8,7)
    ┃  8  ┃         self._x = x;
    ┃     ╋             ^
    ┗━━━━━┻━ 


ymir1: fatal error: 
compilation terminated.


By using the keyword mut, the method can be applicable to mutable instance. In that case the instance refered to by self is mutable, and its mutable fields can be modified. Such methods can be accessed only using mutable object instance - not only mutable reference, the borrowed data located on the heap must be mutable. To call such method, aliases is necessary and cannot be implicit.

class A {
    let mut _x : i32;

    pub self (x : i32) with _x = x {}

    pub def setX (mut self, x : i32) {
        self._x = x;
    }    

}

def main () {
    let a = A::new (12);
    a.setX (42); 

    let dmut b = A::new (12);
    b.setX (42);

    (alias b).setX (42);    
}


The first call at line 14 is not possible, a having only read access to the object instance it contains. The second error, the call at line 17, is due to the fact that even b have write permission to the object instance it contains, it cannot be passed to the method implicitely, and have to be aliased, in order to certify explicitely that the user is aware that the value of the object contained in b will be modified by calling the method. The errors returned by the compiler are the following.

Error : the call operator is not defined for (a).setX and {i32}
 --> main.yr:(14,9)
14  ┃     a.setX (42); 
    ╋            ^  ^
    ┃ Error : discard the constant qualifier is prohibited, left operand mutability level is 2 but must be at most 1
    ┃  --> main.yr:(14,9)
    ┃ 14  ┃     a.setX (42); 
    ┃     ╋            ^
    ┃     ┃ Note : implicit alias of type &(main::A) is not allowed, it will implicitly discard constant qualifier
    ┃     ┃  --> main.yr:(14,2)
    ┃     ┃ 14  ┃     a.setX (42); 
    ┃     ┃     ╋     ^
    ┃     ┗━━━━━┻━ 
    ┗━━━━━┻━ 

Error : the call operator is not defined for (b).setX and {i32}
 --> main.yr:(17,9)
17  ┃     b.setX (42);
    ╋            ^  ^
    ┃ Error : discard the constant qualifier is prohibited, left operand mutability level is 2 but must be at most 1
    ┃  --> main.yr:(17,9)
    ┃ 17  ┃     b.setX (42);
    ┃     ╋            ^
    ┃     ┃ Note : implicit alias of type mut &(mut main::A) is not allowed, it will implicitly discard constant qualifier
    ┃     ┃  --> main.yr:(17,2)
    ┃     ┃ 17  ┃     b.setX (42);
    ┃     ┃     ╋     ^
    ┃     ┗━━━━━┻━ 
    ┗━━━━━┻━ 


ymir1: fatal error: 
compilation terminated.


We have seen in the chapter about function, and more specifically about uniform call syntax that the operator :. can be used to pass a aliased value as the first parameter of a function. This operator is also applicable for method calls, and is to be prefered to the syntax (alias obj).method which is a bit verbose.

def main () {
    let dmut a = A::new (12);

    a:.setX (42); // same as (alias a).setX (42)
}


Method mutability is also applicable inside the body of a method. In the example below, the method foo is mutable, and call another mutable method bar, it also calls a immutable method baz. Implicit aliasing is mandatory when calling the method bar, but not when calling the method baz.

class A {
    let _x : i32;
    pub self (x : i32) with _x = x {}

    pub def foo (mut self, x : i32) {
        self:.bar (x); // :. is mandatory
        self.baz (x);
    }

    pub def bar (mut self, x : i32) {
        self._x = x;
    }

    pub def baz (self) {
        println ("X : ", self._x);
    }
}


Contribution: Calling a immutable method with explicit alias is possible. Maybe that is not a good idea, and will lead to the use of the operator :. all the time, and misleading read of the code.

1.3. Method mutability override

It is possible to define two methods with the same prototype, with the only exception that one of them is mutable and not the other. In that case the method with the best affinity is choosed when called. That is to say, the mutable method is called on explicitly aliased object instances, and the immutable method the rest of the time.

import std::io

class A {

    pub self () {}

    pub def foo (mut self) {
        println ("Mutable");
    }

    pub def foo (self) {
        println ("Const");
    }       
}

def main () {
    let dmut a = A::new ();
    a.foo ();
    a:.foo ();
}


Results:

Const
Mutable

results matching ""

    No results matching ""