Wednesday 29 February 2012

Standard Library (3 of 5). Metaprogramming


Metaprogramming allows the programmer to manipulate basic Libretto entities (packages, classes, functions and fields) as mutable data structures. The tools, which are outlined in this section, can affect the basic object model of the program. Since they are powerful and brutal, it is recommended to use them with great caution and only when necessary.


Libretto is designed to follow the principle: metaprogramming is necessary only for solving ‘metatasks’, that is, those tasks, which really demand code self-modification, or a non-monotonic modification of the object model. In all other cases the regular Libretto tools are more preferable. For instance, if we need to add new method foo to the existing class CC, it is not necessary to modify the class definition, because we can define foo as an external method:
def CC foo = …
Similarly external fields are used, which allow the programmer to add new fields to existing classes:
var CC foo

Entities

The basic entities of Libretto are classes, functions, fields, objects, types and packages. All such entities are instances of the predefined class Entity, which is a superclass of the classes Class, Function, Field, Object, Type and Package.

Some methods are associated with the class Entity, for instance,

  • name returns the name of the entity
  • rename renames the entity
  • getObject returns the object of the entity
  • etc.

The entity class hierarchy is shown below. In brackets, some methods associated with these classes are mentioned:
Entity [name, rename, getObject]
  Class [create, do, classes, addSubclass, fieldsDirect]
  Function [do, undo, addFunctionDef, domain, range]
    PartialFunction [isDefinedAt]
    ExternalMethod
    InternalMethod
    AnonymousFunction
  Object [do, undo, owners]
  Field [addValues, domain, range]
    ExternalProperty
      Property
      Map
    InternalProperty
  Package [addSubpackage]

Classes

Classes in Libretto are declarative instances of the class Class. To illustrate metaprogramming tools, let us consider some methods associated with classes in more detail.

The method newClass creates a new class in a certain package (by default – in the current package) with the name passed as the first parameter. For instance,
newClass(“MyClass”)
is equivalent to
class MyClass
The method addSubclass declares a class as a superclass of another class. For instance,
newClass(“C”)
newClass(“D”)
%C.addSubclass(%D)
is equivalent to
class C
class D extends C
The method classes with signatures
    def Class classes: Class*
    def Object classes: Class*
    def Package classes: Class*
returns all classes of an entity (a class, an object or a package). For instance,
package b
class A
class B extends A
class C extends B
class D
object obj extends C, D

getPackage(“b”).classes  //  A B C D
%C.classes  //  A B
%A.classes  //  ()
obj.classes  //  A B C D
The method classesDirect return those classes, which are directly associated with the context value, e.g. direct superclasses of a class, or classes, which the current object directly belongs to (not via inheritance), or the top classes of a package:
package b
class A
class B extends A
class C
object obj extends B, C

getPackage(“b”).classesDirect  //  A  C
%B.classesDirect  //  A
%A.classesDirect  //  ()
obj.classesDirect  //  B C
The method fields with signatures
    def Class fields: Field*
    def Object fields: Field*
returns the fields, which are associated with an entity (either a class, or an object). For instance,
class C(fix n: Int)
class D(fix c: C)
class E(fix s: String) extends C, D
object obj extends C, D
%C.fields  //  C/n
%E.fields  //  E/s  D/c  C/n
obj.fields  // C/n  D/c
The method fieldsDirect returns the fields, which are directly associated with a class:
class C(fix n: Int)
class D(fix c: C)
class E(fix s: String) extends C, D
%E.fieldsDirect  //  E/s
A lot of other methods are associated with classes including

  • classFields returns all fields associated with the class
  • objectsDirect returns all instances created by the class constructor
  • objects returns all instances of the class
  • removeSubclass retracts the class as a subclass of another class
  • subclassesDirect returns all direct subclasses of the class
  • subclasses returns all subclasses of the class
  • subclassof is a filter letting those entities through, which are subclasses of the class
  • new is the method that calls the constructor of the class.

etc.

Functions

Functions in Libretto are declarative instances of the predefined class Function:
class Function extends Entity
The method do determines the behavior of a function. For instance,
def plus(n: Int) = this + n
is syntactic sugar for
object plus extends Function {
  def do(n: Int) = this + n
}
The notation for anonymous function %(x){x + 1} is syntactic sugar for
Function() # {def do(x) {x + 1}}
Since the class Function is underdetermined, it is always necessary to reify the abstract function do.

Some other methods are associated with function entities, for instance,

  • addFunction adds an external function to a package
  • addMethod adds a method associated with a class
  • addFunctionDef adds a new definition to a polymorphic function
  • removeFunction removes an external function from a package
  • removeMethod removes a method associated with a class

etc.

Fields

Field entities are the instances of the class Field. The set of methods working with fields as entity objects, in particular, includes:

  • addValues adds values to a field
  • domain returns the domain type of a field
  • maxCard returns the maximum allowed number of values
  • addFixField creates a new immutable field associated with a class or an object
  • addVarField creates a new mutable field associated with a class or an object
  • range returns the range type of a field

Objects

Here are some methods working on objects.

  • addObjectClass adds an object to a class
  • fields returns the fields associated with an object
  • newObject creates a new object
  • owners returns objects, a field of which has the current object as its value
  • removeObjectClass removes an object from a class
  • removeValue removes a value from an object field
  • removeValues removes all values from an object field
  • setValues assigns new values to an object field
  • values returns all values of an object field (or of all fields of the object)

Types

The types are used for typing entities and solving some other metaprogramming tasks. The definition of the class Type is:
class Type(fix cls: Class, crd) extends Entity {
  fix card: Int = crd match {
    case c if Int => c
    case s if StringSymbol and s == '* => -1
    case _ => 
       error(“Invalid cardinality value {crd} in type declaration”())
  }
}
In metaprogramming, types are used to define other entities, e.g. for setting the function/field domains and ranges.

Packages

The programmer can work with packages as entities, create and delete them. The examples of methods are:

  • getPackage returns a package with a certain name (or the current package)
  • newPackage creates a new package
  • package returns the package, in which a given entity is defined
  • packages returns all loaded packages.

A Metaprogramming Example: Operator enum

Let us define the operation enum, which converts a sequence of string symbols into an enumeration type. For instance, the execution of the query
'WeekDay enum ('Mon, 'Tue, 'Wed, 'Thu, 'Fri, 'Sat, 'Sun)
must be equivalent to the following code:
class WeekDay

object Mon extends WeekDay
object Tue extends WeekDay
object Wed extends WeekDay
object Thu extends WeekDay
object Fri extends WeekDay
object Sat extends WeekDay
object Sun extends WeekDay

fix %WeekDay values = (Mon, Tue, Wed, Thu, Fri, Sat, Sun)
Note that the external field values is defined on the entity %WeekDay, an object of the class Class.

Now, define:
def (enumer: StringSymbol) enum (els: StringSymbol*) {
  try {
    fix cls = newClass(enumer)  // enum class
      
    // create and add elements of enum
    fix enums = els.newObject.addObjectClass(cls)

    // create field ‘values’
    fix values = cls.addFixField(“values”)
    cls.addValues(values, enums)
  }
  catch {case _ => error("failed to create enum {enumer}"!)}
}
Let us verify:
'WeekDay enum ('Mon, 'Tue, 'Wed, 'Thu, 'Fri, 'Sat, 'Sun)

%WeekDay.values  //  Mon Tue Wed Thu Fri Sat Sun

def WeekDay isWorkingDay = (not this in (Sat,Sun))

fix class Event(day: WeekDay, comment: String)
def Event isWorkingDayEvent = day.isWorkingDay

Event(Mon, “Meeting”).isWorkingDayEvent  //  true

No comments:

Post a Comment