Monday 20 February 2012

Basic Libretto Constructs (4 of 5). Objects

An object in Libretto is a container incapsulating attributes and behaviors (methods) of a domain entity. Objects are instances of classes, which specify their general patterns. There are two kinds of objects in Libretto: declarative and dynamic.

Declarative Objects

Declarative (named) objects are defined statically, similarly to classes but with the keyword object. For instance, the declaration
class Person(fix name: String)
object JOHN extends Person(“John”)
creates a declarative object JOHN, which is accessible by its name:
JOHN.name  //  “John”
Each declarative object is the only representative of an anonymous class (called a singleton), which is created simultaneously with the object.

Information inherited from classes can be redefined and augmented in objects:
class C(fix n) {
  def plus = n + 1
  def func
} 

object cc extends C(5) {
  override def plus = n + n
  def func = “I’m func”
  var mmm = 115
} 
cc.n  //  5
cc.mmm  //  115
cc.plus  //  10
cc.func  //  “I’m func”
In this example the underdetermined class C contains the abstract method func, which is reified on cc. Besides, the method plus is overridden in cc, and the individual field mmm is introduced. The following sections contain a more detailed description of these constructs.

Declarative objects are the components of the object model, in which they are defined. As a rule their lives corresponds to the life cycle of the whole object model. In particular, they can not be deleted without metaprogramming tools.

Dynamic Objects

The structure and behavior of dynamic objects are not different from those of declarative ones. The basic features of dynamic objects are:

  • A dynamic object does not have a name, so it is inaccessible directly, but only via variables or the fields of other objects. 
  • A dynamic object can not exist independently. It can exist only while accessible from some variable or a declarative object via the chain of field values. Otherwise the dynamic object is deleted by the garbage collector.

Dynamic objects are anonymous and created by class constructors or factory methods (see section Constructors and Factory Methods), for instance,
class Person(fix name: String)

{
  fix john = Person(“John”)
  fix j = john
  j.name  // “John”
}
It is guaranteed that the created object Person(“John”) is alive while the variables john and j are alive. The operator # integrates compound object constructors, e.g.
class Item(fix name: String)
class Colored(fix color: String)

fix greenItem = Item(“myItem”) # Colored(“green”) # {fix owner = “me”}
Here greenItem inherits from two classes and has the individual field owner.

Dynamic objects can serve as field’s values in other objects:
class People(var crowd: Person*)
object MyCrowd extends People(Person(“Marie”), Person(“Paul”))
Now
MyCrowd.crowd.name  //  “Marie”  “Paul”

Individual Fields and Methods

Fields and methods can be defined on objects – in the same way as in classes. For instance, instead of the code
class Person(fix name: String)
class People(fix crowd*)

object MyCrowd extends People(Person(“Marie”), Person(“Paul”))
we can define
class Person(fix name: String)

object MyCrowd {
  fix crowd* = (Person(“Marie”), Person(“Paul”))
}
with the same result:
MyCrowd.crowd.name  //  “Marie”  “Paul”
In this example the field crowd is defined within an individual object. The behavior of such a field is similar to that inherited from a class.

Similarly individual methods are introduced:
class Num(fix n: Int)

object NUM115 extends Num(115) {
  def plus(n: Int) = @n + n
}

NUM115.plus(5)  //  120 
Dynamic objects also allow the definition of individual fields and methods, for instance:
class Person(fix name: String)

{
  var person = Person(“Jim”) # {  
    def petName(suffix: String) = name + suffix
  }

  person.petName(“my”)  //  “Jimmy”
}
Here the method petName is defined at the moment, when the dynamic object is created. The operator # combines the components of the object constructor.

Like in classes, a redefinition of an existing method in an object must be indicated by the keyword override.
class Bird {
  def canFly = “yes”
}

object HUMMINGBIRD extends Bird
object PINGUIN extends Bird {
  override def canFly = “no”
}
HUMMINGBIRD.canFly  //  “yes”
PINGUIN.canFly  //  “no”
Objects can directly inherit from underdetermined classes. For instance, let us introduce the underdetermined class, which implements a string stream
class StringStream {
  def read: String
}
Now an instance of this class can be created, which is linked to a concrete string:
object myline extends StringStream {def read = “abcd”.char}
The predefined method char divides the string into the sequence of singleton strings:
myline.read.size  //  4
Dynamic objects also can be instances of underdetermined classes:
{
  fix myline = StringStream() # {def read = “abcd”.char}
  myline.read  //  “a” “b” “c” “d”
}
Since the class StringStream is underdetermined, the second component of the object constructor reifies the abstract method.

In dynamic objects, the methods also can be overridden:
class C(fix n: Int) {
  def mult = n * 2
}

{
  fix b = C(5)
  fix c = C(5) # {override def mult = n * 3}
  b.mult  //  10
  c.mult  //  15
}
External methods can be defined in the context of declarative objects (see section External Methods on Objects). External methods have priority over the regular methods:
class C(fix n: Int) {
  def mult = n * 2
}
object bb extends C(5)
object cc extends C(5)

def cc mult = n * 3

bb.mult // 10
cc.mult // 15
External methods can not be defined in the context of dynamic objects.

Multiple Inheritance and Objects

Object creation is based on multiple inheritance.

Declarative Object Creation

The structure of a declarative object definition with multiple inheritance is similar to that of a class definition:
class Person(fix name: String)
class Student {
  fix school: String
}

object JOHN extends Person(“John”), Student {
  school = “Oxford University”
}
The query:
JOHN. «{name} studies at the {school}»!
  // “John studies at the Oxford University”
If class(es), from which a declarative object inherits, are underdetermined, then it is necessary to reify the abstract entities, for instance,
class GeomFigure {
  def square
}
object MyFigure extends GeomFigure {
  def square = 15.2
}

Dynamic Object Creation

Multiple inheritance for dynamic objects is defined by the operator #, which can combine several class constructors:
class Person(fix name: String)
class Student(fix school: String)

{
  var studs = Person(“Marie”) # Student(“MIT”)
  studs += Person(“Gretchen”) # Student(“CAU”)

  studs.«{name} studies at {school}»!
    //  “Marie studies at MIT”
    //  “Gretchen studies at CAU”
}
If a class is underdetermined, then it is necessary to define the abstract entities. Here are a couple of examples:
class C {def f()}
class D {def g}

{
  fix cd = C() # D() # {def f() = 5; def g = 10}
}

…

class Colored(fix color: String)
class GeomFigure {
  def square: Real
}

{
  fix coloredFigure = Colored("red")#GeomFigure()#{def square = 15.2} 
  fix simpleFigure = GeomFigure()#{def square = 1.1}
}
The application of # is necessary only if the components of an object constructor affect the structure of the object itself (e.g. add a new field or a superclass, that is, perform ‘metaprogramming’ operations). Let us compare:
class D(var d)

{
  fix dd1 =  D() # {fix val = 5; d = val}
  fix dd2 =  D() {fix val = 5; d = val}
  
  dd1.val  //  5
  dd2.val  //  ERROR: Field or method val not found for object dd2
}
In the first assignment, val is interpreted as a new field, which augments the structure of the object dd1. In the second assignment val is interpreted as a local block variable used for the modification of the field d, but val itself is not added as a field to the object stored in dd2.

Methods and fields inherited from classes can be combined with the individual methods and fields of the object:
class C {
  def f1
  def f2 = “abc”
  fix n
}

{
  fix x = C() # {
    def f1 = 5
    override f2 = “def”
    def g = 6
    n = 10
    fix m = 15
  }
}
Here an instance of the class C is created and saved in the variable x. In this instance, f1 is reified, f2 is redefined, and g is defined, the field n is instantiated, and the individual field m is created and instantiated.

Operator %

Each entity of Libretto (a class, a function, a field or a package) is represented by an object with additional functionality. In Libretto the following rule is applied:

If an object has additional functionality (e.g. represents a function, a class, or another entity), then its name is always interpreted as the name of this entity.

For instance,
object obj
object fun {
  def Any do = “Hello”
}
obj  //  obj
fun  //  “Hello”
Here the method do makes the object fun a function. In order to work with such an object explicitly, the operator % should ba applied:
fun  //  “Hello”
%fun  //  fun

No comments:

Post a Comment