Sage-Code Laboratory
index<--

Eve OOP

Eve is an object oriented language. We define objects and classes. A class is a user defined data type, that encapsulate data structure and methods. An object is an instance of a class. We use classes to create data structures and algorithms.

Objects are state machines that are instantiated on demand and released from memory when they are no longer needed. You can create global objects using "set", or local objects using "new" keyword.

Classes are objects themselves. Also, classes can be used to generate object instances. All object instances generated from a class, will inherite class structure and behaviour. The most important characteristics of objects are:

Notes: Objects can't be abstract but you can define an abstract class that can be extended by other classes or user defined data types. Eve implements all 4 OOP principles.

Page bookmarks:


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.

#define an object instance
** declare a class with two attributes
class MyClass = {a, b: Integer} <: Object;

** create several class signatures in a "class region"
class
  Foo = {attr:Type, ...} <: Object;
  Bar = {attr:Type, ...} <: Object;
  ...

Notes:

Object type

Eve is similar to Java. It has a root Object that is actually a composite data type. This can be used as a base class. The Object constructor accept variable number of arguments. You can initialize an object by using the Object constructor.

#define an object instance
process test:
  ** initialize object using Object() constructor
  new object_name := Object(attribute1:value1, 
                            attribute2:value2, 
                            ...);
  ...
return;

Note: Object constructor can receive argument names that do not exist, and bind values to new attributes using (key:value) pairs. This is possible in Eve due to a gradual typing system.

Object methods

Methods are related to an object or a class. The methods can have parameters and can return results. You must define all methods in the same module as the class declaration.

Example


# define a "Point" class:
driver test_point:

class Point = {x, y :Double} <: Object; 

method (@self:Point) move(a, b: Double):
  let self.x += a;
  let self.y += b;
return;

method (@self:Point)  string() => (string:String):
  let string := "# = (#:#)" ? (self.id, self.a, self.b);
return;

process
  ** initialize the points
  new p := Point(x:1, y:2);

  ** move the point
  p.move(2,1);
  print p.string(); -- (3:3)
return;

Note: To call a method, you do not use any keyword. Just specify the method name with arguments. Methods can be created in packages to extend the Eve language with domain specific actions.

Object Attributes

A class can define attributes for future object instances. The class constructor must set initial values for object attributes and can create new private attributes.

To access public attributes you must use dot notation like:

object_instance.public_attribute;

To access private attributes you must use "self." qualifier. Private attributes are available in constructor and inside of object methods but are not available with object name as qualifier. That is, private attributes are encapsulated.

self.private_attribute;

Note A class does not know anything about its objects. You can not ask the class anything about its instances, except if you create a special logic using class properties.

Class members

A class can have properties and methods. Class properties and metods are static members and must be defined inside the class declaration region.

An object can have attributes and methods. Attributes are defined by the constructor. Object methods are defined later in the same module, outside of the class body. Object attributes and methods are dynamic.

Class Properties

A class can have public or private properties. Each property is defined in the class context. We declare class properties after symbol ":" before any class method.

Class Methods

You can define methods inside of a class body. 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 class methods:

Example:

Next example demonstrate a class that has no attributes and no constructor. This kind of class can't be instantiated. Is an abstract class, but it can be inherited by other classes.


driver test_method:

# define a class that has no constructor
class ClassName <: Object:
  ** class propetyes
  set _sum: Integer;  -- protected
  set last: Integer;  -- public

  ** class method (change state) 
  method change(param:Type):
    let last := param;
    let _sum += param;
  return;

  ** create a getter for _sum
  method sum() => (@result:Integer):
    let result := _sum;
  return;
return;

process
  ** access class method
  ClassName.change(10); 
  ClassName.change(12); 

  ** inspect ".last" property
  expect ClassName.last  == 12; 
  expect ClassName.sum() == 22; 
return;

Module Methods

You can define methods in modules that are usable with any basic type similar to class methods. Also, you can define general methods. These methods are not bound to a particular data type.

Example:


module console:

** module method 
method print (*args:Variant):
  ...
return;

** module initialization
init
  ...
return;

Note:Module methods will be useful, when we create the system library. Is enaugh to say we have left the design open, so that we can bootstrap Eve later.

Class Tree

Using the "Object" as the "root class", you can create a hierarchy of classes. Each classes is derived from the Object or from other "superclass" forming a "class tree". Like a real tree the class hierarchy has a single root.

Class Tree

Class Tree

Class Constructor

You can create more complex objects by using a class constructor. This is a special struture that can be used to describe class members and object instances. A class can have a constructor or can be constructor-less.

Design:

A class is a composite data type. It can be derived from another data type or from the root Object. Next design pattern explain how to declare a class with attributes and constructor:


# user defined class   
class NewType = {attributes} <: Object:
  ** declaration
  ... 
create (parameters) => (@self :NewType):
  ** constructor
  ...    
release
  ** cleanup
  ...
return;

Notes: A class has a declaration region where you can define static members, and an initialization region "create". This region is called contructor. A class can have one single constructor. The constructor is optional.

Parameters:

Constructors can have one or more parameters, some may be optonal. 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:
   ** define class properties
   set property_name := value;
   ....

   ** define class method
   method test() => (result:Type):
      ...
      let result := expression;
   return;
create(parameters) => (@self: ClassName):
  ** conditional initialization
  if condition then
    let self.attribute = value;
    ...
  else
    let self.attribute = other_value;
    ...
  done;
return;

Variadic constructor

A constructor can have parameters with an initial value that are optional. Parameters with no initial values are mandatory. It is possible to have a variable number of arguments, received as a HashMap for a vararg based constructor.

Example:


# define a class derived from root Object
class NewName += {attribute:Type, ....} <: Object;
  ** class body
  ...
create(*parameters: HashMap) => (@self:NewName):
  ...
return;

process
  ** instantiate a local object using constructor
  new object := NewName(param:value,...);
  ...
return;

Note:The object in the previous example, has type: NewName. So you can use the class name as a data type. The data type defined by the class is dynamicly created by the compiler.

@self object

Object named "self" is the current object name. This is the result of the class constructor. It uses prefix "@" to show that it is a reference. The "self" parameter is used for declaring object methods. In Java this is an implicit parameter. In Eve you must declare @self explicitly.

Example:

This example defines a "Point" class with two parameters. The parameters have default values so they are optional when you create a point. Read the example and the notes to understand how to use a user defined class constructor.


# demonstrate a class
driver point_demo:

** define class Point, from root Object
class Point = {x, y :Double} <: Object:
   ** no members
create(x = 0, y = 0 :Double) => (@self: Point):
  ** constructor 
  let self.x := x;
  let self.y := y;
return;

# using the class
process 
  ** initialize the points
  new p1 := Point(x:1, y:2);
  new p2 := Point(x:2, y:2);

  ** use string patterns to print attributes
  print "p1 = (x:#n, y:#n)" ? (p1.x,p1.y);
  print "p2 = (x:#n, y:#n)" ? (p2.x,p2.y);
return;

Output:

p1 = {x:1, y:2}
p2 = {x:2, y:2}

Example:

This example defines a class that can track its instances explicit. You can use static properties of a class to record all instances newly created. On release, you can remove the object instance @self from the item list.


# demonstrate a self tracking class
driver self_track:

** define class Point, from root Object
class Point = {x, y :Double} <: Object:

** tracking information
  set instances: Integer;
  set items: ()Point;  -- list of points

** constructor receive 2 parameters
create(x = 0, y = 0 :Double) => (@self: Point):
  ** alter attribute values
  let self.x := x;
  let self.y := y;
  ** add object to list of items
  let items += @self;
  let instances += 1;  
release(@self: Point):
  ** remove the object from list
  let items -= @self;
  let instances -= 1;
return;

process 
  ** initialize the points
  new p1 := Point(x:1, y:2);
  new p2 := Point(x:2, y:2);

  ** verify how many point
  expect Point.instances == 2;

  ** use item list to print all points
  cycle:
    new i := 0;
  for p in Point.items loop
    let i += 1;
    print "p#n = (x:#n, y:#n)" ? (i, p.x,p.y);
  repeat;
return;

Output:

p1 = {x:1, y:2}
p2 = {x:2, y:2}

Comparing objects

We can use two comparison operators with objects: "is" and "==". First operator "is" will compare the object location. If the objects have the same location they represent the same object. Second operator: "==" compare object type and object attributes or value. There are complementar operators "is not" and "!=". That can be used to create conditionals.

Example:

In next examples we use a primitive type: Integer, that is actually a class. So any Integer number is an Object instance. Object instances are references and can be compared.


#object comparison demo
driver object_compare:
  set m = n = 1: Integer; 
process
  ** equal values and types
  expect   m   == n;
  expect   not (m != n);

  ** not the same location
  expect   not (m is n);
  expect   m is not n;

  ** alter one value
  let n := 2;

  ** equal values and types
  expect   m   != n;
  expect   not (m == n);
return;

Inheritance

Eve support inheritance and pilymorphism. You can specify a supertype using the symbol "<:" that is equivalent to the keyword: "extend" used in Java. Using this symbol makes a class that has all attributes and methods of the super-class or super-type.

Pattern:


# define a class with inheritance
class BaseType = {attributes} <: Object;

** object method
method (@self:BaseType) demo(param:Type, ...):
   ...
return;

** create a descendent of BaseType
class NewType += {new_attributes} <: BaseType:
  ** class members
  ...
create(parameters) =>(@self: NewType):
  ** call base class constructor
  let self := super(parameters);

  ** create new attributes
  new self.new_attribute := value;

  ...
return;

** overwriten method
method (@self:NewType) demo(parameters) => (result:Type):
   ...
return;

Generics

Generics are Eve system lybraries. You can create sub-types using a generic. Next generics are pre-defined: {Array, List, DataSet, HashMap}. We will describe these data types in next chapter.

Use cases

Eve enable 4 generic types. If more generic types are required in the future we will modify the language. We will describe these generics as collection types. All these generics are "Iterable" Objects.


** making an array
class UserArray = []Type <: Array;

** making a list
class UserList = ()Type <: List; 

** making a data set
class UserSet = {}Type <: DataSet; 

** making a dictionary
class UserMap = {}(Type:Type) <: HashMap; 

List of DataSet

First we define the ElementType, then we define a collection UserList. After this we define a new instantes of type UserList and we create new elements in this list.


# Using a generic type to create a table
driver test_generic:

** making an element type
class ElementType = {a,b,c: Integer} <: DataSet;

** making a list of elements
class UserList = ()ElementType <: List; 

process
  ** instantiate UserList object 
  new myList := UserList(); 

  ** enqueue one member
  let myList += ElementType(1,2,3);

  ** append one member
  let myList := myList + ElementType(7,8,9);

  print myList -- ({1,2,3},{7,8,9})
return;

Type Inference

Using a data literal you can shortcut the design pattern. This is possible due to type inference. Eve create the types for you. After collection is created you can add new elements.


** define object using type inference
   new myList := ({1,2,3},{7,8,9});

** using introspection to find the type
   print Type(myList);       -- List
   print Type(myList[1]);    -- DataSet
   print Type(myList[1][1]); -- Integer

JSON literals

Using JSON you can create complex data structures. In next example we create a list of objects. Each object can have same structure or different structure. This is possible due to dynamic nature of Objects.


driver catalog:

class Person = {name:String, age:Integer} <: Object;

global
   set myList: ()Person; 

process
** define object using type inference
   let myList := (
      {name: "Elucian", age: 56},
      {name: "Daniel" , age: 45}
   );

** using introspection to find the type
   expect Type(myList)          == List;
   expect Type(myList[1])       == Person;
   expect Type(myList[1].name)  == String;
   expect Type(myList[1].age)   == Integer;
return;

Note: In a collection you can use objects of the same type or descendents types of the declared element type. In this case if you derive a type Employe from Person, then you can add Emplyes to this list.

Read next: Functions