Eve Objects
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;
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 withnew 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