– Typee Documentation –
3 – Typee Language Description
3.12 – Classes
The class concept is at the core of any OOP (Object Oriented Programming) language. We will not discuss this concept here. It will be longer explained later in this documentation. We just present here the associated syntax in Typee.
For a quick and concise presentation of the concept of classses, please have a look to this fine introduction from Guido Van Rossum: Classes – Python 3.7 documentation.
3.12.1 – Class Definition
The definition of a class automatically creates in Typee an object of type class
for which the identifier is the identifier of the defined class.
The formal EBNF specification of class definition in Typee is:
<class definition> ::= 'class' <identifier> [<template def>] <inheritance> <class statements block> <access qualifier> ::= 'hidden' | 'local' | 'private' | 'protected' | 'public' <dotted name> ::= <identifier> ( '.' <identifier> )* <inheritance> ::= [ ':' <inheritance item> (',' <inheritence item>)* ] <inheritance item> ::= [<access qualifier>] <dotted name> [<template args>] <scalar type> ::= 'bool' | 'char' | 'char16' | 'float32' | 'float64' | 'int8' | 'int16' | 'int32' | 'int64' | 'uint8' | 'uint16' | 'uint32' | 'uint64' | 'str' | 'str16' | '_float_' | '_int_' | '_uint_ | '_numeric_' <template args> ::= '<' <template args'> '>' <template args'> ::= [<expression> ( ',' <expression> )*] <template def> ::= '<' <template def'> '>' <template def'> ::= (<identifier> | <const qualifier> <template def const name>) ( ',' (<identifier> | <const qualifier> <template def const name>) )* <template def const name> ::= <scalar type or dotted name> <identifier> [ '=' <expression> ] <class statements block> ::= '{' [<class statements list>] '}' <class statements list> ::= <class statement> ( <class statement> )*
All of the statements put in the body of the class (i.e. its statements block) will be attributes declarations, and methods and operators definitions. Global access protection statements may be inserted in there also.
In some other languages (Python, for instance), there may be other instructions put in the definition of a class. Actually, this is not the case in Typee but it might become legal in a next version of the language.
When we will introduce constructors, later in this documentation, we will show that these very specific methods have a dedicated syntax. Let’s introduce it shortly here.
3.12.1.1 Constructors
A constructor is a method which gets the same identifier than its embedded class and which returns a reference to an object that gets as its type the class name. This object is said to be an instance of the class.
The definition of a constructor does not need then any declaration of type.
Example:
class MyClass{ MyClass(){ me.val = 0; } MyClass( const int32 value ){ me.val = value; } MyClass( const MyClass other ){ me.val = other.val; } const int32 getVal(){ return :private: int32 val; }
In the above example, the constructor of class MyClass
is overridden and gets three different signatures. Notice that none of these definitions include a type declaration. Meanwhile, method getVal()
is definitively typed, as should any method other than constructor.
3.12.1.2 Forward declarations
The class body (i.e. its block of statements) constitutes a name space that is considered by Typee as a whole. Everything that is declared at any place in this name space is considered to be known from everywhere in this name space. So, any declared or defined item is visible form all other ones whitin the block of statements.
This means that there is no need to insert forward declarations at the head of a class body. In the above example, about class MyClass
, local private attribute val
is visible within the whole class definition, even if declared at the far end of its body. It is the legal in Typee to use as well as assign it in the constructors which are defined before its declaration.
3.12.1.3 Accessing attributes and methods
When an instance of a class needs to reference one of its attributes or methods, keyword me
has to be used as a prefix of the attribute or method identifier, both separated with a '.'
. This keyword is the equivalent of this
in Java or self
in Python. This is the reason why attribute val
is always preceded by me
and a dot in all constructors.
3.12.1.4 Operators
When a class implements or overrides an operator, there is no need to reference it as a method with keyword me
!
Just use the operator “identifier” as is.
Example:
class C : MyClass { C( const int32 value ){ MyClass( value ); } int32 operator pre ++(){ return ++me.val; } } C my_c = C( 1 ); print( my_c, ++my_c, ++my_c ); // prints "1 2 3"
3.12.2 – Class Declaration
Classes may be declared before being defined. This will always be a forward declaration.
The formal EBNF specification of class declaration is:
<class declaration> ::= <forward> 'class' <identifier> <template def> [<inheritance>] ';' <forward> ::= 'forward' | 'fwd' <inheritance> ::= [ ':' <inheritance item> (',' <inheritence item>)* ] <inheritance item> ::= [<access qualifier>] <dotted name> [<template args>] <template args> ::= '<' <template args'> '>' <template args'> ::= [<expression> ( ',' <expression> )*]
Example:
forward class Item; class Container { // ... none append( Item item ) { // ... } } class Item { // ... }
3.12.3 – Templates
Templating will be fully explained later in this documentation. We just describe here the templating syntax for classes in Typee.
Just notice that templates help declaring types and constant values to be used anywhere in the body of a class while being specified at its use-time. The formal EBNF specification for the class templating syntax in Typee is:
<template def> ::= '<' <template def'> '>' <template def'> ::= (<identifier> | <const qualifier> <template def const name>) ( ',' (<identifier> | <const qualifier> <template def const name>) )* <template def const name> ::= <scalar type or dotted name> <identifier> [ '=' <expression> ]
This list of types and constants declarations is enclosed within angle brackets and is put jeust afetr the class identifier. Example:
fwd class Container <ItemType, const uint16 MAX_SIZE=span class="nu"300>;
The declaration of instances of the above class is quite simple. Example:
Container <uint64> cont_ui64; // 300 max. items of type uint64 forward class Item; Container <Item, 16> my_cont; // 16 max. items of type Item
3.12.4 – Inheritance
Inheritance is a concept inherent to OOP. We will fully explained it later in this documentation. Let’s first envisage its Typee syntax.
<inheritance> ::= [ ':' <inheritance item> (',' <inheritence item>)* ] <inheritance item> ::= [<access qualifier>] <dotted name> [<template args>]
The inheritance of super classes in Typee is introduced by the colon sign (“:
“). It is placed after the template definition (if present) or after the class identifier (if no templates definition is present) and it precedes the brace bracket (“{
“)that opens the body of a class in its definition or the semi-colon (“;
“) that ends a class forward declaration.
When present, the inheritance declaration is a list of class identifiers separated by commas (“,
“).
Example:
forward class A; forward class B; forward class C: A, protected B; forward class D: hidden C; forward class E: D;
In the above example, class C
inherits from classes A
and B
. Moreover, every attribute, method and operator definied in class B
will get a protected access in class C
and every class inheriting from C
.
Class D
, inheriting from class C
with a hidden priviledge (i.e. access protection), gets access to everything that will have been declared as public or protected in classes A
, B
and C
. Meanwhile, any class inheriting from D
will get no access (no visibility) to items defined in classes A
, B
and C
. This is the case for class E
in the above example.
3.12.5 – Visibility of Attributes and Methods
Access protection qualifiers are used to set the visibility of attributes and methods along the chain of classes inheritance.
<access qualifier> ::= 'hidden' | 'local' | 'private' | 'protected' | 'public'
They may be used in two ways:
- at declaration time of a variable, a method or an operator, or
- globally for all next declarations.
Let’s explain this just via two examples:
Example 1
classMyClass { MyClass(){ me.set_val( 0 ); } public const uint32 get_val() { return me.val; } protected none set_val( const uint32 value ) { me.val = value; } hidden uint32 val; }
In this first example, the constructor of MyClass gets public
visibiliy / access since no access protection qualifier is specified in its definition and since public access protection is the default.
Method get_val()
is specified as being public
and as so, is visible from everywhere class MyClass()
is instantiated.
Method set_val()
is specified as being protected
. This means that this method may be called anywhere within class MyClass()
as well as within any of classes that inherit from it, but that it cannot be called elsewhere.
The attribute val
of class MyClass()
is specified as being hidden
. This means that this attribute can only be read and modified in class MyClass()
. Any attempt to read or write this attribute outside the body of class MyClass()
will raise an error. The only way to get access to this hidden attribute is to call methods set_val()
and get_val()
.
Example 2
class MyClass { :public: MyClass(){ me.set_val( 0 ); me.val_publ = 0; me.val_prot = 0; } const uint32 get_val() { return me.val; } :protected: protected none set_val( const uint32 value ) { me.val = value; } :hidden: uint32 val ; public uint16 val_publ; protected uint16 val_prot; }
In this second example, everything that is declared under :public:
and above :protected:
gets public visibility.
Everything that is declared under :protected:
and above :hidden:
gets protected visibility.
And everything that is declared after :hidden:
gets not visible unless otherwise specified directly in declaration: val_publ
is public
and val_prot
is protected
.
3.12.6 – Instantiation
This concept has been largely used in above examples, while not having yet explained. A class is intantiated when its constructor (whatever its signature) is called with its related arguments. There are two ways of using the class instantiation concept : in place or by assignment.
3.12.6.1 Instantiation Assignment
This is the most common usage of instantiation. An object identifier is assigned with a call of the constructor of a class. This object may then reference any attribute or method declared or defined in the class, as well as it can be applied any operator defined in the class.
Example:
class MyClass { :public: MyClass( const uint32 value ){ me.val = value; } none display(){ print( me.val ); } :private: uint32 val; } MyClass my_object = MyClass( 10 ); my_object.display(); // prints "10"
3.12.6.2 In-place Instantiation
This is not the most common way to use instantiation, but it is a very pratical one. There, the generated instance is immediately used, in-place. Example:
class MyClass { :public: MyClass( const uint32 value ){ me.val = value; } none display(){ print( me.val ); } :private: uint32 val; } MyClass( 20 ).display(); // prints "20"
Here, an instance is locally created at the very last statement, it is used to reference then method display()
, and then it is forgotten for the rest of the code.
Well, forgotten is not truly exact here. It is rather destroyed as soon as call to display()
returns.
We explain instances destructions in next sub-section.
3.12.7 – Instances Destruction
Any instance of a class remains active and present in memory (stack or heap, according to the targeted programming language) until it gets no more visible or unitl it gets destroyed.
An instance is visible everywhere in the block of statements it is defined in, which means that it is visible also in every nested blocks of statements. Once the program execution quits this upper block, the instance is automatically destroyed in Typee – this may mean in some targeted language that this instance is ready to be garbage collected.
Programmers may also explicitely destroy any instance of a class. This is the purpose of the delete statement which formal EBNF specification in Typee is:
<del statement> ::= ( 'del' | 'delete' ) <identifiers list>
<identifiers list> ::= <dotted name> ( ','
<dotted name> )*
In any of these two cases, Typee will translate the instance destruction in the targeted programming language with a call to the destructor of the class it instantiates.
The destructor is a specific method, which gets a specific identifier and which can be defined at most once per class. Its formal EBNF spectification in Typee is:
<destructor> ::= 'none' 'destroy' '(' ')' <statements block>
It is mostly used to release any reserved or allocated resource during the instance processing life. Example:
class Container< ItemType, const uint16 MAX_SIZE=300 > { Container(){ me.content.allocate( MAX_SIZE ); // this allocates memory space me.current_length = 0; } none destroy(){ // releases any memory space allocated at instantiation time delete none append( ItemType item ){ me.content[ me_current_length++ ] = item; } const uint16 length(){ return me.current_length; } array< ItemType > content; uint16 current_length ; }
Notice: When a class inherits from other classes (named super classes), Typee automatically calls in the translated source code the destructors of every super classes that this class inherits from.
Last but not least, the definition of a destructor is not mandatory. It may be that classes do not allocate any resource and do not need then to release them, for instance.
When deleting the instance of such a class, no destructor is called – and nothing happens then, but stack removal or heap removal or later garbage collection of this instance, according to the targeted programming language.
3.12.8 – Accessing attributes and methods
The dot sign (‘.‘) put after the identifier of an object (i.e. en instance of a class) indicates that next identifier identifies an attribute or a method of the instantiated class.
Example
class MyClass{ :public: MyClass( const uint8 value ){ me.set( value ); // references methods set() } none set( const uint8 value ){ me.val = value; // references attribute val } uint8 val; } MyClass my_obj = MyClass( 1 ); print( my_obj.val ); // prints "1" my_obj.set( 2 ); print( my_obj.val ); // prints "2"
3.12.9 – Class attributes and methods
As for most OOP (Object Oriented Programming) languages, classes may own class attributes and class methods. Keyword static
has to preceed their declarations. They may then be accessed directly without any need of instantiations, as well as from instantiations. Here again, sign dot (‘.‘) is used to access these class attributes and methods.
Such attributes are unique to ALL instantiations of the class and their values are shared by every instantiations.
Example:
class MyClass{ :public: MyClass( const uint8 value ){ me.set( value ); // references class method set() } static none set( const uint8 value ){ // this is a class method me.val = value; // references class attribute val } static uint8 val; // this is a class attribute } MyClass my_obj = MyClass( 1 ); print( my_obj.val ); // prints "1" my_obj.set( 2 ); print( MyClass.val ); // prints "2" MyClass.set( 3 ); print( my_obj.val ); // prints "3" MyClass a_new_obj( 4 ); print( my_obj.val ); // prints "4" since attribute // val is shared by every instances
3.12.10 Methods overriding
Typee accepts the overriding of methods in classes that inherit from parent classes. This is the default for every methods that are declared in a class: they all can be overridden in inheriting classes. C++ programmers: this is the virtual
functionnality.
Example:
class ClassA { uint32 f ( float32 v ){ return uint32( 2.0 * v ) + 1; } } class ClassB : ClassA { uint32 f ( float32 v ){ return uint32( v ) + 1; } } ClassA a = ClassA(); ClassB b = ClassB(); print( a.f(10.2), b.f(10.6) ); // prints "21 11"
To make a method not overridden in inheriting classes, specify if with keyword final.
Example:
class ClassA { final uint32 f ( float32 v ){ return uint32( 2.0 * v ) + 1; } } class ClassB : ClassA { /*** not allowed; would set an error at translation time uint32 f ( float32 v ){ return uint32( v ) + 1; } ***/ } ClassA a = ClassA(); ClassB b = ClassB(); print( a.f(10.2), b.f(10.6) ); // prints "21 21"
3.12.11 Checking Class belonging
Is some object an instance of a class? This is usely checked with function isinstance()
or isinstanceof()
in other OOP languages.
In Typee, syntax is simpler:
<is instance of> ::= <dotted name> '->' <dotted name>
Evaluates to true
if first identifier (<dotted name>
) is an instance of a class for which the identifier is the second one.
Example:
class ClassA {} class ClassB : ClassA {} ClassA a; ClassB b; print( a -> ClassA, a -> ClassB, b -> ClassB, b -> ClassA ); // prints true false true true
Next section formerly explains