Eve Classes
Keywords of this phase
The keywords that phase 4, Object Oriented Features, adds to the core language (the core keywords are in Syntax). Each one is explained on the page in the last column.
| Keyword | Meaning | Explained in |
|---|---|---|
class | declares a class, an ordinal or a function type; a class body closes with end Name; | Classes |
constructor | declares the constructor of a class, inside its body; its result is the new object, @self | Classes |
destructor | declares the cleanup of an object, inside the class body | Classes |
method | declares a method of a class, or an extension method outside it | Methods |
trait | declares a stateless set of required and provided methods that classes adopt | Partials |
public | visibility of a class member: seen everywhere | Classes |
protected | visibility of a class member: the class and its subclasses | Classes |
private | visibility of a class member: the class only (the default) | Classes |
Class signature
Class is a type declaration. You can declare the attributes of future objects before implementing the class. This is called "forward declaration". It is useful when you have 2 classes to be defined that depend on each other.
Classes are objects themselves, singleton objects actually. Also, classes can be used to create object instances. All object instances created from a class, will inherit class structure and behaviour. The most important characteristics of objects are:
#define an object instance
** declare a class with two attributes
class MyClass = {a, b: Integer} <: Object;
...
Features:
Eve language implement all 4 oop principles:
- Encapsulation: each object has its own states;
- Inheritance: an object inherit its base class;
- Polymorphism: an object can play its base class role;
- Abstraction: eve supports abstract classes and traits, made of partial methods;
Notes:
Class are user defined data types.
- Aspects can define only private classes;
- Modules can define classes and export them:
export (Point);(see Modules); - Class names always start with uppercase letter.
Class members
A class can have properties and methods. Class properties and methods are static members and must be defined inside the class declaration. Some classes do not have a class declaration body. These classes can have properties defined in the signature between brackets {} but can't have methods.
Class Properties
A class can have properties. We declare class properties after symbol ":" before any class methods in the class body. Class properties are by default private. Public properties are declared with the word public. Usually a class use private properties, getters and setters if necessary.
- Properties are defined using keyword "set" ;
- A class is a singleton Object of type Class;
- Class properties are visible in all class methods;
- The visibility of a member is a word in front of its declaration (see Visibility).
Visibility
Eve is verbose: the visibility of a class member is an English word written in front of its declaration, never a sign in its name.
| Word | Visible in | Example |
|---|---|---|
public | every script that can use the class | public method area(@self) => (@result: Real) is |
protected | the class and its subclasses | protected set registry: ()Shape; |
private | the class only (the default, when no word is written) | private method check(@self) is |
Methods belong to classes: a method is declared in the body of a class, or, as an extension method, at the level of a module or an aspect, with @self of the class type as its first parameter. Code that does not belong to a class is written as functions (with a result) and procedures (without a result); a function whose result can differ for the same arguments ends with !.
Class Methods
You can define methods inside of a class body. These are "class methods". Class methods have access to all class members, without using any qualifier but have no access to object instance. @self object is not available in the class methods, unless explicit injected as input/output parameter.:
Example:
Next example demonstrate a class that has no attributes and no constructor. This kind of class can't be instantiated. Is a prototype class. The purpose of a prototype class is to create a singleton object.
primary aspect test_method is
** define a class that has no constructor
class ClassName <: Object is
** class properties
private set sum: Integer; ** private property
public set last: Integer; ** public property
** method (change state)
public method change(param: Integer) is
let last := param;
let sum += param;
return;
** getter method
public method total() => (@result: Integer) is
let result := sum;
return;
end ClassName;
process main is
** access class method
ClassName.change(10);
ClassName.change(12);
** inspect ".last" property
expect ClassName.last == 12;
expect ClassName.total() == 22;
return;
end test_method;
Only methods in a class
A class hosts only methods (and its constructor and destructor). A function or a procedure in a class body is a compile error. A helper of the class is a private method, which the other methods call on self, or an ordinary function declared outside the class. The methods may use lambda expressions.
class Circle = {r: Real} <: Object is
** a helper is a private method
method square(@self, x: Real) => (@y: Real) is
let y := x * x;
return;
public method area(@self) => (@a: Real) is
let a := 3.14159 * self.square(self.r);
return;
end Circle;
Only a method is bound to an object. The parameter @self belongs to methods, inside the class or outside it as an extension method. A function or a procedure can't have @self: function total(@self: Point) => (...) is a compile error. To add behavior to a class from outside, declare a method.
Methods are not values. A method can't be handled like a function: there are no references to methods and no method type, so a method can't be passed as an argument, stored in a variable or returned. Code that needs the work of a method as a callback passes a lambda that calls it: circles.map((c) => (c.area())) (see Callbacks).
Class variables
"A class hosts only methods" is about subprograms, not about data. A class can also hold class variables: private variables declared with set in the class body, one copy for the whole class (not one per object). The constructor and every method of the class can read and write them with the qualified name ClassName.VarName.
Code outside the class, such as a function or a procedure, can't touch a class variable directly. It must call an accessor method (a getter or a setter) that the class offers. This way the class controls every change of its own state.
primary aspect test_class_variables is
class Account = {owner: String} <: Object is
** class variables: private, shared by all the accounts
private set count: Integer;
private set limit: Integer;
constructor(owner: String) => (@self: Account) is
let self := Object();
let self.owner := owner;
let Account.count += 1; ** ClassName.VarName in the constructor
return;
** accessors: the only way for outside code to reach the variables
public method total() => (@result: Integer) is
let result := Account.count; ** ClassName.VarName in a method
return;
public method setLimit(value: Integer) is
let Account.limit := value;
return;
end Account;
function report() => (@text: String) is
** let text := Account.count; ** compile error: count is private
let text := "accounts: " + Account.total();
return;
process main is
new a := Account("Ana");
new b := Account("Dan");
expect Account.total() == 2;
expect report() == "accounts: 2";
return;
end test_class_variables;
Do not confuse a class variable with a property of the signature: owner belongs to each object, while count belongs to the class and is shared.
Object Constructor
You can create more complex objects by using a "constructor". In Eve we name things like they are used in specific programming paradigms. The constructor is a special subprogram that can be used to create a class instance also known as object. A class can be constructor-less in this case you can't create instances of this kind of class.
Design:
Next design pattern explain how to declare a class with attributes and constructor:
# user defined class
class NewType = {attributes} <: Object is
** declaration
...
** constructor: declared inside the class body; its result is the new object
constructor([parameters]) => (@self) is
let self := Object(); ** the construction: always the first statement
...
return;
** destructor is symmetric with constructor
destructor(@self) is
** cleanup internal states
...
return;
** object method: @self needs no type inside the class
method attached(@self) is
** add extra functionality to the class
....
return;
end NewType;
Notes: A class can have a single constructor, with optional parameters. The constructor and the destructor are declared inside the class body. They look like methods except they use the "constructor" and "destructor" keywords and have no name: the name of the class is known. The parameters of the constructor receive the arguments of the class call, Point(1, 2). Its result is @self, the new object, without a type: it is always an object of the class. A method and the destructor receive the object as their parameter @self; the constructor produces it, so @self is in its result list (see Construction).
Object Methods
A class instance can have attributes and methods. Class instances are objects. The constructor and the object methods are declared in the class body. An object method receives the object as its first parameter, @self. The constructor is a subprogram whose result is the new object.
Parameters
Constructors can have one or more parameters, some may be optional. It can also have vararg parameters and input/output parameters like any method (see Subprograms). You can use a decision statements to initialize the object attributes in different ways based on parameter values.
# define a class
class ClassName = {attributes} <: Object is
** define class properties
set property_name := value;
....
** define class method
public method test() => (@result:Type) is
...
let result := expression;
return;
** constructor with default and vararg parameter, that is a list of integers
constructor(param1 := value1, *param2: ()Integer) => (@self) is
let self := Object();
** conditional initialization (flexible constructor)
if condition do
let self.attribute := value;
...
else
let self.attribute := other_value;
...
done;
return;
end ClassName;
Construction
The object of a class is created in two places that work together: the class call outside, new p := Point(1, 2);, and the constructor inside the class. The constructor is a subprogram whose result is the new object. Like any result, it is declared in the header, => (@self), and receives its value in the body:
class Shape = {name: String} <: Object is
constructor(name: String) => (@self) is
let self := Object(); ** the root of the chain allocates the object
let self.name := name; ** and initializes the part of Shape
return;
end Shape;
class Circle = {radius: Real} <: Shape is
constructor(name: String, radius: Real) => (@self) is
let self := Shape(name); ** the superclass builds the object and returns it
let self.radius := radius; ** Circle initializes its own part
return;
end Circle;
process main is
new c := Circle("wheel", 2.5); ** c is a Circle
return;
Step by step, for new c := Circle("wheel", 2.5);:
- The class call
Circle(…)starts the construction with the target class Circle and runs the constructor of Circle. Nothing is allocated yet:self, the result of the constructor, is still empty; - The first statement of Circle,
let self := Shape(name);, calls the constructor of the superclass, in the same construction; - The first statement of Shape,
let self := Object();, is the root of the chain: the object is allocated here, once, with the memory of the target class:nameandradius, both with their zero values; - Shape sets
self.nameand returns: its result becomes the result ofShape(name), that is theselfof Circle; - Circle sets
self.radiusand returns:creceives the object, a Circle.
Rules
- The result of a constructor is
@self, written in its header:constructor(parameters) => (@self) is. Its type is the class, so it is not written; - The first statement of a constructor is its construction:
let self := Object();in a class derived directly fromObject,let self := Superclass(arguments);in a subclass. A constructor without it, or with a second one, is a compile error; selfis empty until the construction: using it before is a compile error. After the constructionselfcan't be replaced;- The outermost class call decides the class of the object. Inside a construction, the call of a superclass constructor does not create a second object: it builds the object of the outermost call. Outside a constructor,
Object()orShape("x")creates a new object as usual; - The attributes declared in the class signature exist after the construction, with their zero values: they are set with
let self.name := value;. An attribute that is not in the signature is created withnew self.extra := value;; - Each constructor has its own
self, its own result, as two functions each have their own local variables; they refer to the same object because one constructor returns it and the other receives it; - An abstract class can be called only from the constructor of a subclass, never with
new.
A class without a constructor is filled by name: new c := Circle(name: "wheel", radius: 2.5); sets the declared attributes, the inherited ones included. A constructor is needed only to check the arguments or to compute attributes; most classes of data, such as the rows of a table, need none.
For the virtual machine, every constructor is an ordinary function that returns an object. The only hidden thing is one argument, the target class: Circle(…) passes it, Shape passes it on, and Object() at the root allocates the memory of that class. Everything else is normal calls and normal results.
Read next: Inheritance