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.
Unlike some other languages (in which the keyword
Field initialization is part of its declaration:
A field can be typed:
Inverse fields are a strong tool, but the programmer should be careful while using them because they can affect efficiency.
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
If for some object the value of an external field is not specified then the empty sequence
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
Operator
The operator
Role 1. Suppose we need to set an interface color to instances of class
Role 2. Consider the following Libretto code:
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
In the second example, the external field
In order to provide both options, two different kinds of external fields are implemented in Libretto:
Declarative external fields are defined as instances of the class
The degree of link softness is the only difference between
Note that the expression
The values of dynamic external fields are also can be typed:
Operator
By using the operator
Field contents and
A Libretto convention recommends to represent ‘is-part-of’ relations by a field named
Operators
Let us consider the example:
Methods also can be declared as private:
These special functions used externally can also define virtual external fields. For instance,
- Fields in Classes and Objects
- Class Field Definition
- Object Field Definition
- Default Field Value
- Inverse Fields
- Immutable Structures
- External Fields
- Declarative External Fields
- Multiple Value Fields and Their Modification
- Operator
->
- Mappings and Duality of External Fields
- Dynamic External Fields
- User-defined External Fields
- Operator
@
and Parametric Fields - Field contents and
&
- Access to Fields
- Virtual Fields
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 = 1Similarly:
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 // 25Field 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 tmpnameIf 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 // 36Only 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 JOHNA 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 expressionfix 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: Class2Here 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: StringNow 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 colorIf 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 // JOHNThe 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 // 10By using
~
it is possible to define the inverse external field, for instance,
JOHN.~grandparent // TOMThe default types of external fields are
Any/Any*
. In particular, the declaration
var Any propname: Any*is equivalent to
var propnameThe 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 // 2Depending 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 // 2Here 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 classExternalField
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) // 120The 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) // 15In 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.contentsThe version with the dot operator is also allowed:
tree.& // “Leaf 1” “Leaf 2”This version is defined in Libretto as follows:
def & = contentsThe 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 totree.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 asprivate
:
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.nHere 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 functionsget_…
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 -3The 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