Eve Objects

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". The class called like a function runs its constructor: new p := Point(1, 2);.

JSON literals

Using JSON you can create complex data structures of objects. 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 is

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

  set myList: ()Person;

  process main is
  ** 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;
end catalog;

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

Comparing objects

We can use two comparison operators with objects: "is" and "==". First operator "is" will compare the object memory address. If the objects have the same address they represent the same object. Second operator: "==" compare object type and object attributes. There are complementary operators "is not" and "<>". They 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 is
  set (m, n) = 1 :Integer;
  process main is
    ** 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
    new n := 2;

    ** equal values and types
    expect   m   <> n;
    expect   not (m == n);
  return;
end object_compare;
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.

Related pages: Classes, Inheritance, Generics, Partials, Methods.

Object type

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

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

Note: Default object constructor can receive argument names like (attribute1, attribute2) that are not declared initially, these bind values to new attributes using (key:value) pairs. This is possible in Eve due to a gradual typing system. This is a minor feature in Eve.

Object Attributes

A class can define attributes, with types in the class signature. The class constructor will set initial values for the object attributes just created, and can create additional attributes explicit in the constructor using "new" statement: new self.extra := 0;.

  • We declare public attributes using = {...} or += {...n} after class name;
  • Public members are declared in the class body with the word public;
  • Object attributes declared in the signature exist after the construction and are set in the constructor with let self.name := value;; an extra attribute is created with new self.name := value;;
  • The attributes of the signature need no word: they are public, read with object.attribute;
  • An attribute created in the constructor or in a method with new self.name := value; is private: it is not in the signature;
  • Code outside the class can't add an attribute to an object: new p.z := 7; is an error. It would break the encapsulation. To have more attributes, declare them in the signature or derive a class.

To access public attributes you must use dot notation:

object_instance.public_attribute;

To access private attributes you must use "self." qualifier available in constructor. Private attributes are available in constructor and methods but are not available with object name as qualifier. That is, private attributes are protected so that developer will not modify them by mistake from outside.

self.private_attribute;

Note A class body does not know anything about its objects or object attributes. You can not ask the class anything about its instances, except if you create a special logic using class properties. Also, class methods, do not receive @self parameter so there is no way to access by mistake an attribute in a class method.

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.


# define a "Point" class with a constructor:
driver test_point is

  ** declare a class "Point"
  class Point = {x, y :Real} <: Object is
    ** class method to calculate distance between two points
    public method distance(p1, p2: Point) => (@result: Real) is
      new dx := p2.x - p1.x;
      new dy := p2.y - p1.y;
      let result := sqrt(dx*dx + dy*dy);
    return;

    ** constructor: its result is the new point
    constructor(x = 0, y = 0 :Real) => (@self) is
      let self := Object();
      let self.x := x;
      let self.y := y;
    return;

    ** object method: receives the point as @self
    public method move(@self, a, b: Real) is
      let self.x += a;
      let self.y += b;
    return;

    ** object method: the point as a string
    public method string(@self) => (@result: String) is
      let result := "({self.x}:{self.y})";
    return;
  end Point;

  process main is
    ** use the constructor with different arguments
    new p1 := Point(1, 2);        ** two-parameter constructor
    new p2 := Point();            ** no-parameter constructor (uses defaults)
    new p3 := Point(p1.x, p1.y);  ** copy the values of p1

    ** print results
    print "p1: " + p1.string();
    print "p2: " + p2.string();
    print "p3: " + p3.string();

    ** demonstrate distance calculation
    new distance := Point.distance(p1, p3);
    print "Distance between p1 and p3: {distance}";
  return;
end test_point;

Output:

p1: (1:2)
p2: (0:0)
p3: (1:2)
Distance between p1 and p3: 0

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. In the destructor, you can remove the object instance self from the item list.


# demonstrate a self tracking class
driver self_track is

  ** define class Point, from root Object
  class Point = {x, y :Real} <: Object is
    ** tracking information (private class properties)
    set instances: Integer;
    set items: ()Point;  ** list of points

    ** constructor receive 2 parameters
    constructor(x = 0, y = 0 :Real) => (@self) is
      let self := Object();
      ** set the attribute values
      let self.x := x;
      let self.y := y;
      ** add object to list of items
      let items <+ self;
      let instances += 1;
    return;

    destructor(@self) is
      ** remove the object from list
      items.delete(self);
      let instances -= 1;
    return;
  end Point;

  process main is
    ** 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
    loop
      new i := 0;
    for p in Point.items do
      let i += 1;
      print "p{i} = (x:{p.x}, y:{p.y})";
    done;
  return;
end self_track;

Output:

p1 = (x:1, y:2)
p2 = (x:2, y:2)

Read next: Classes