Sunday 19 February 2012

Basic Libretto Constructs (3 of 5). Fields

A field is a piece of data encapsulated within an object. A field can be defined either in a class constructor (and then it characterizes all instances of the class) or in an object definition (and then it is an individual characteristic of this object). Fields are part of object models used by Libretto programs.


Fields in Classes and Objects

Fields in Libretto can be of two kinds:

  • immutable (specified by the keyword fix), and 
  • mutable (specified by the keyword var). 

Unlike some other languages (in which the keyword val is used for immutables) in Libretto fix is adopted, to clearly distinguish it from var. Immutable fields take their value when created and never change it.

Field initialization is part of its declaration:
class C {
  var x = 115
  fix y = 120
}
In this example two fields are defined in the class C – mutable x and immutable y. Now
object c extends C
c.x  //  115
c.x = 0
c.x  //  0
c.y  //  120
c.y = 0  //  ERROR: Attempt to reassign a fixed field.
An object cannot be replaced by another object as a value of an immutable field, but the contents of this object can change:
class C(var n: Int)
class D(fix obj = C(5))
object d extends D()

d.obj.n  //  5
d.obj.n = 10
d.obj.n  //  10
d.obj = C(6)  //  ERROR: Attempt to reassign a fixed field.
Immutable fields are more secure and make the program more efficient, so the general recommendation is to use them instead of var fields whenever it is possible.

A field can be typed:
class C {
  var str: String = “Hello”  // the type of String
  fix x: Int = 5
}
The fields str and x can contain at most one (zero or one) value. A sequence of several objects can be stored in a field, only if its type is marked by the asterisk:
class C {
  var ints: Int* = (1,2,3,4)
  var all: Any* = (“a”, 1)
}
By default, fields are typed as Any, so the following two declarations are equivalent:
var one: Any = 1
var one  = 1
Similarly:
var all: Any* = (1,2,3) 
is equivalent to
var all*  = (1,2,3)
Fields can be declared in class constructors and in object definitions.

Class Field Definition

In a class constructor fields can be declared

  • in the block of parameters of the constructor (name in the following example)
  • in the body of the constructor (age):

class Person(fix name: String) {
  var age: Int
}


object JOHN extends Person(“John”)
JOHN.age = 25
JOHN.name  // “John”
JOHN.age  //  25
Field values can be specified via parameter variables:
class Person(tmpname) {
  fix name = tmpname
}
The parameter variable tmpname is used to pass a value into the constructor. Unlike fields, parameters are marked by neither var nor fix. Parameter variables in class constructors behave as immutable:
class Person(tmpname) {
  fix name = tmpname
  tmpname = tmpname + “!”
}
  // ERROR: Attempt to reassign fixed variable tmpname
If a field and a variable have the same name, then by default this name is interpreted as the variable. The operator @ is used to explicitly specify the name as the field name:
class Person(name) {
  fix @name = name
}
A field in the parameter block can be assigned a default value. Then the parameter corresponding to this field can be omitted in constructor calls:
class Person(fix name, var age = 16)
Person(“Ann”).age  //  16
Person(“John”, 36).age  //  36
Only right-to-left fields can be assigned the default values:
class Person(var age: Int = 15, fix name: String)
//  ERROR: field name is uninstantiated

Object Field Definition

Fields can be also introduced in object definitions. This is the example of a declarative object with an individual field:
object JOHN extends Person {
  fix hasACar = “Ford”
}
JOHN.hasACar  //  “Ford”
The object JOHN has additional field hasACar, which is linked solely to this object. Such a field also can be defined in a dynamic object (see section Dynamic Object Creation):
{
  var marie = Person() # {fix hasACar = “Porsche”}
}
The operator # combines the components of the dynamic object definition.

Default Field Value

If a field is not explicitly initialized, its value by default is the empty sequence () – independently of the field type:
class C {
  var n: Int
  var m*
}
C().n  //  ()
C().m  //  ()

Inverse Fields

By using ~ the programmer can specify a virtual field inverse to a given field. Let us define
class Person(fix name, fix age)
object JOHN extends Person(“John”, 30)
object MARIE extends Person(“Marie”, 16)
Then
JOHN.name  //  “John”
“John”.~Person/name  //  JOHN
(16,30).~Person/age  //  MARIE JOHN
A compound name is used as the name of the inverse field. It includes the class name, in which the field is defined, and the name of the field itself. The compound name is necessary because a field name is local within a class and the explicit class indication provides its global uniqueness.

Inverse fields are a strong tool, but the programmer should be careful while using them because they can affect efficiency.

Immutable Structures

Immutable structures are objects, all fields of which are immutable. Immutable structures are useful in a range of situations. In particular, they behave similarly to algebraic types in functional programming. Libretto offers a special syntax for immutable structures. The expression
fix class Person(name: String, hasChild: Person*)
defines a class of immutable structures. The keyword fix at the fields defined in such structures is always omitted. This definition is syntactic sugar for
class Person(fix name: String, fix hasChild: Person*)
def %Person undo(x: Person) = (x.name, x.hasChild)
The method undo is automatically defined on the class Person. It allows the programmer to apply pattern matching to immutable structures (see section Operator match and Pattern Matching).

External Fields

External fields are entities of Libretto, which behave as regular fields, while being external constructs to objects they are defined on. Like external methods, they are defined independently of classes. There are two kinds of external fields: declarative and dynamic.

Declarative External Fields

A declarative external field has a global name and is part of the object model of the program. Declarative external fields are declared on the package level. A declaration of an external field has the form:
var Class1 propName: Class2 
Here the external field propName is defined in the context of the class Class1 and can take values from the class Class2. An external field is an instance of the predefined class Property, so the declaration above is syntactic sugar for
object propName extends Property(%Class1, %Class2)
(here %Class1 is the object corresponding to the class Class1, see section Operator %). Various kinds of external fields are considered in section Mappings and Duality of External Fields.

External fields allow the programmer to add attributes to objects, while not affecting the object internal structure. They are convenient for solving some problems, for which otherwise metaprogramming tools had to be involved. For instance, let us define the class Ninja together with two instances of this class
class Ninja(fix name)

fix drew = Ninja("Drew")
fix adam = Ninja("Adam")
Suppose that later we need to add to this class a field color. Thanks to declarative external fields the programmer can do this without modifying the structure of the class Ninja, and thus, without involving metaprogramming tools. Let us define
var Ninja color: String
Now assign values to the external field:
drew.color = “black”
adam.color = “black”
Handling external fields is similar to handling regular fields:
drew.name  //  “Drew”
drew.color  //  “black”
Define an external method
def Ninja hasColor = “{name}’s color is {color}”!
Now
drew.hasColor  //  “Drew’s color is black”
adam.hasColor  //  “Adam’s color is black”
The behaviors of regular name and external color are not distinguishable, although actually the data saved in the external field color is stored in a special structure beyond the instances of the class Ninja.

If for some object the value of an external field is not specified then the empty sequence () is returned. For instance,
fix donnie = Ninja(“Donnie”)
donnie.color  //  ()
If an external field is not defined in the context of a class, then an error occurs:
“donnie”.color  //  ERROR: wrong object type for external property color
If necessary, such situations can be handled by the special function missing_ (see section Method missing_). For instance,
def String missing_(s: String) = “{this} does not have {s}”! 

“donnie”.color  //  “donnie does not have color”
Here missing_ is defined as an external method.

External fields are convenient for handling objects, which are imported from external libraries. Besides external fields can play the role of hash tables and various mappings (see section Mappings and Duality of External Fields). For instance, an external field grandparent defined in the context of the class Person provides mapping from persons to their grandparents.
fix class Person(hasChild: Person*)
var Person grandparent: Person  //  external field

object TOM extends Person
object PAUL extends Person(TOM)
object JOHN extends Person(PAUL)
Now the query
(TOM, PAUL, JOHN) as gp. hasChild. hasChild {grandparent = gp} 
sets a mapping between grandparents and grandchildren (the operator ‘as' is defined in section Operator as). Now,
TOM.grandparent  //  JOHN
The programmer can determine the values of an external field explicitly. For this, the operator -> is used (see section Operator ->):
var Person age: Int {
  TOM -> 10
  PAUL -> 30
}
TOM.age  //  10
By using ~ it is possible to define the inverse external field, for instance,
JOHN.~grandparent  //  TOM
The default types of external fields are Any/Any*. In particular, the declaration
var Any propname: Any*
is equivalent to
var propname
The latter declaration resembles a local variable introduction. But there is no ambiguity, because declarative external fields are always defined globally on the level of packages, where local variables are not allowed.

Multiple Value Fields and Their Modification

The type declarations of external fields can contain the asterisk – just like regular field declarations. Such fields can store arbitrary object sequences. There is a variety of assignment operators, which can modify the contents of such fields:
var String children: String*

“John”.children = (“Jane”, “Marie”)
“John”.children += “Tom”
“John”.children .= “Ann”
“John”.children  //  “Ann”  “Jane”  “Marie”  “Tom”
“John”.children ?[$ == “Tom”] .= “Paul”
“John”.children  //  “Ann”  “Jane”  “Marie”  “Paul”  “Tom”
“John”.children ?[$ != “Paul”] --  
“John”.children  //  “Paul”
The sequence handling techniques are explained in more detail in section Assignments, Indexes and Predicates.

Operator ->

The operator -> adds a pair key/value to an external field.
var nums {
  1 -> “one”
  “two” -> 2
}

1.nums  //  “one”
“one”.nums  //  ()
“two”.nums  //  2
Depending on the external field type, the operation -> either substitutes the value of this key, or augments it:
var Int nums1: String {
  1 -> “one”
  1 -> “ein”
  1 -> “uno”
}

var Int nums2: String* {
  1 -> “one”
  1 -> “ein”
  1 -> “uno”
}
The second field is different from the first one by capability to keep sequences of more than one value. Now:
1.nums1  //  “uno”
1.nums2  //  “one”  “ein”  “uno” 

Mappings and Duality of External Fields

The design of external fields allows them to play two roles in Libretto:

  • the role of object fields;
  • the role of key/value data structures like maps and hash tables.

Role 1. Suppose we need to set an interface color to instances of class Foo. For this, an external field color can be introduced and handled as if it were an in-class Foo field:
var Foo color: String

object foo extends Foo
foo.color = “green”
…
In this example, the external field color plays the role of a regular in-class field.

Role 2. Consider the following Libretto code:
fix class Person(name: String, surname: String)

var String persons: Person* {
  “John” -> Person(“John”, “Smith”)
  “Ann” -> Person(“Ann”, “Jacobs”)
  “Ann” -> Person(“Ann”, “Air”)
}
Now,
“John”.persons.surname  //  “Smith”
“Ann”.persons.size  //  2
Here the external field persons plays the role of a map, which links persons’ names with their objects.

Thus, external fields can play two conceptually different roles within a uniform syntactic scheme. However, there are some semantic differences. In particular, the important issue is whether key/value links are soft or hard? In other words, should such links prevent an object from being deleted by the garbage collector? In the first example the object Foo() is not connected with any structures except the external field color. In this case the GC can remove the object, because the information about color is secondary, and there are no other access methods to Foo(). Regular fields behave similarly – they are removed as soon as their object is deleted.

In the second example, the external field persons plays the role of a map, which collects data about persons, and provides access to this data by their names. It is obvious that in this case the garbage collector must not delete Person instances to avoid dangling links.

In order to provide both options, two different kinds of external fields are implemented in Libretto: 

  • the class Property sets soft key/value links, which do not protect from the GC; 
  • the class Map sets hard key/value links, which protect from the GC.

Declarative external fields are defined as instances of the class Property, so they do not protect from the GC. This means that the external field color is defined correctly, because color should not prevent the object Foo() from being deleted by the GC. The external field persons is not defined correctly, because the declaration
var String persons: Person*
is syntactic sugar for
object persons extends Property(%String, Type(%Person, '*))
(Type(%Person, '*) is an instance of the class Type, see section Types). Since persons should provide hard links, it must be defined as follows:
object persons extends Map(%String, Type(%Person, '*)) {
  “John” -> Person(“John”, “Smith”)
  “Ann” -> Person(“Ann”, “Jacobs”)
  “Ann” -> Person(“Ann”, “Air”)
}
Now the objects connected to persons are protected from deletion.

The degree of link softness is the only difference between Property and Map.

Dynamic External Fields

An external field can be created as a dynamic object. For instance,
{
  fix prop = Map()
  1.@prop = “one”

  2.@prop = “two” 
  1.@prop  //  “one”

  fix x = prop
  2.@x  //  “two”
  “two”. ~ @x  //  2
}
The dynamic field created here is ‘anonymous’. It is accessible via variables. The operator @ interprets its parameter as a field and applies this field to the context. The latter query shows that dynamic fields can be inverted.

Note that the expression 1.prop does not apply the field from variable prop to the context 1, because independently of the context a variable is always interpreted as its value (see section Dot Operator Generalization). Thus, 1.prop == prop.

The values of dynamic external fields are also can be typed:
{
  fix prop = Property(%String, %Int)
}

User-defined External Fields

In Libretto, a underdetermined class ExternalField is predefined, which specifies the general model of external fields:
class ExternalField(fix domain: Class, fix range: Type) {
  def put(x, y)
  def get(x)
  def containsKey(x)
  ... 
}
The predefined classes Property and Map inherit from ExternalField. By using ExternalField the programmer can define her/his own implementations of external fields. We can define, for instance, an external field mybase
object mybase extends ExternalField(%String, %String) {

  def get(x: String) = 
        httpGet(“http://foo.com/mybase.ltt?query={x}”!)
  def put(x, y) 
        httpGet(“http://foo.com/mybase.ltt?key={x};value={y}”!)
  def containsKey(x: String) = 
        httpGet(“http://foo.com/mybase.ltt?haskey={x}”!)
}
which provides interaction with a remote database located at the address http://foo.com/mybase.ltt. Now, to store data in the remote database, we just add pairs to the external field:
mybase {
  “John’s name” -> “Smith”
  “Tom’s name” -> “Jacobs”
}
The next query reads data from the database:
“John’s name”.mybase  // “Smith”
The two latter queries are independent of the external field implementation – whether it has a regular Map implementation, or represents a remote database. In particular, we can substitute the above mybase definition with
object mybase extends Map(%String, %String)
without revising the rest of the code.

Operator @ and Parametric Fields

By using the operator @(propName), where propName is a string expression or a field object (see section Operator %), the programmer can have access to fields dynamically, on the fly. For instance,
var extprop // external field
class C(fix abc: Int)

C(115).@(“a”+“bc”)  //  115
C(115).@(%abc)  //  115

1.@(“extprop”) = 120
1.extprop  //  120
1.@(%extprop)  //  120
The operator @ is also convenient for separating fields from other entities and parameters. For instance,
class C(var abc)
def assign(abc) {@abc = abc}
object c extends C(10)

c.assign(15)
c.abc  //  15
c.@abc  //  15
c.@(%abc)  //  15
In the definition of assign the field and parameter abc are distinguished by @. In Libretto an expression starting with @ is always interpreted as a field.

Field contents and &

A Libretto convention recommends to represent ‘is-part-of’ relations by a field named contents. It allows the programmer to use some helpful tools for handling sub-objects. Consider the following definition of a tree, which contains nodes and leaves:
class Tree 
class Leaf(fix elem) extends Tree {
  override def toString = “Leaf {elem}”!
}
class Node(contents: Tree*) extends Tree {
  override def toString = “Node [{contents.toString.join(“, ”)}]”!
}
The subnodes of a tree node are accessible via the field contents. Introduce a tree
object tree extends Node(Leaf(1), Leaf(2))
tree.toString  //  “Node [Leaf 1, Leaf 2]”
tree.contents.toString  //  “Leaf 1”    “Leaf 2”
The symbol & is a shorthand for “.contents”:
tree.contents  //  “Leaf 1”   “Leaf 2” 
tree&  //  “Leaf 1”   “Leaf 2”
Note that & can be easily defined as an external method:
def (x)& = x.contents
The version with the dot operator is also allowed:
tree.&  //  “Leaf 1”   “Leaf 2”
This version is defined in Libretto as follows:
def & = contents
The double ampersand && defines the transitive closure of contents – it collects all objects reachable from the current object via the chains of contents. For instance,
object deepTree extends 
                 Node(Node(Leaf(1), Leaf(2)), Node(Leaf(3),Leaf(4)))

deepTree  //  “Node [Node [Leaf 1, Leaf 2], Node [Leaf 3, Leaf 4]]”

deepTree&  
  //  “Node [Leaf 1, Leaf 2]”
  //  “Node [Leaf 3, Leaf 4]”

deepTree&&  
  //  “Node [Leaf 1, Leaf 2]”
  //  “Leaf 1”
  //  “Leaf 2”
  //  “Node [Leaf 3, Leaf 4]”
  //  “Leaf 3”
  //  “Leaf 4”
The method && correctly works in structures containing loops (e.g. arbitrary directed graphs). It is also easily definable in Libretto, for instance:
def (x)&& = x.chains([]).contents

def chains(col: List) {
  if (not this in col) {col += this}
  contents?{case _ => ()}.chains(col)
  col
}
The local trap ?{case _ => ()} hunts leaves, because they do not have the field contents.

Operators & and && can be used with a class name, and then they select only instances of the specified class:

  • tree &Node is equivalent to tree.contents?[Node]
  • tree &&Node is equivalent to the transitive closure of “.contents?[Node]” applied to tree.

Let us consider the example:
object yetAnotherTree extends Node(Leaf(1), Node(Leaf(2),Node(Leaf(3))))

yetAnotherTree &Leaf  //  “Leaf 1”
yetAnotherTree &Node  //  “Node [Leaf 2, Node [Leaf 3]]”
yetAnotherTree &&Leaf  //  “Leaf 1  Leaf 2  Leaf 3”
yetAnotherTree &&Node  //  “Node [Leaf 2, Node [Leaf 3]]   Node [Leaf 3]”
The methods for & with class filtering are also easily defined in Libretto:
def (x)&(%prop) = x.contents?[prop()]

def (x)&&(%prop) = x.chains([], prop).contents
def chains(col: List, prop) {
  if (this?[prop()] and not this in col) {col += this}
  contents?{case _ => ()}.chains(col, prop)
  col
}
The syntactic technique based on contents plays a key role in a number of predefined Libretto constructs like the class List (see section Class List) and XML/HTML document handling (see section XML and HTML Processing). In particular, the use of & and && makes HTML/XML navigation as compact and expressive as the corresponding path navigation in such a DSL as XPath.

Access to Fields

By default, regular and external fields are public. This means that global access to them is allowed. But access can be restricted by declaring them as private:
class Loan {
  fix borrower: Person
  private fix loan: Int
  def isBig = loan > 1000
}
In this example the precise value of loan is not accessible globally, but the method isBig allows us to check if the loan is big or small.

Methods also can be declared as private:
class C {
  private def f = “f is called”
  def g = f 
}

C().f  //  ERROR: Method f is private in class C
C().g  //  “f is called”
Entities in packages can be declared private as well. Such entities are not reachable from other packages:
package mine

private class Number(var n:Int)
private String map: Number {
  “one” -> Number(1)
  “two” -> Number(2)
}
def String getByString = map.n
Here the class Number and the external field map are inaccessible globally, but the public function getByString allows retrieving data from them:
import mine.*
“one”.getByString  //  1

Virtual Fields

The programmer can define virtual fields, which have the desired behavior. This is done by the special functions get_… and set_… . In section Getters and Setters it was shown how to apply them to regular fields.

These special functions used externally can also define virtual external fields. For instance,
class Person(fix name: String)

private var Person ageValue: Int  // external field

// external getter
def Person get_age = ageValue 

// external setter
def Person set_age(a: Int) =
  if (a > 0) {ageValue = a} else error(“Wrong age value {a}”!)

object JOHN extends Person(“John”)

JOHN.age = 15
JOHN.age  //  15
JOHN.age = -3
  //  ERROR: Wrong age value -3
The external field ageValue is private and inaccessible globally. Thus, only the virtual external field age can be globally used for handling age values.

No comments:

Post a Comment