Eve Classes

A class is a user defined data type that encapsulates a data structure and its methods. An object is an instance of a class (see Objects). This page shows how a class is declared: its signature, its members, their visibility and its constructor. Classes can be extended (Inheritance), left incomplete (Partials) and made generic (Generics).

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.

KeywordMeaningExplained in
classdeclares a class, an ordinal or a function type; a class body closes with end Name;Classes
constructordeclares the constructor of a class, inside its body; its result is the new object, @selfClasses
destructordeclares the cleanup of an object, inside the class bodyClasses
methoddeclares a method of a class, or an extension method outside itMethods
traitdeclares a stateless set of required and provided methods that classes adoptPartials
publicvisibility of a class member: seen everywhereClasses
protectedvisibility of a class member: the class and its subclassesClasses
privatevisibility 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.

WordVisible inExample
publicevery script that can use the classpublic method area(@self) => (@result: Real) is
protectedthe class and its subclassesprotected set registry: ()Shape;
privatethe 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);:

  1. 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;
  2. The first statement of Circle, let self := Shape(name);, calls the constructor of the superclass, in the same construction;
  3. 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: name and radius, both with their zero values;
  4. Shape sets self.name and returns: its result becomes the result of Shape(name), that is the self of Circle;
  5. Circle sets self.radius and returns: c receives 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 from Object, let self := Superclass(arguments); in a subclass. A constructor without it, or with a second one, is a compile error;
  • self is empty until the construction: using it before is a compile error. After the construction self can'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() or Shape("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 with new 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