a generic reification technique

26
A Generic Reication Technique for Object-Oriented Reective Languages Rémi Douence , Mario Südholt École des Mines de Nantes Département Informatique, 44307 Nantes cedex 3, France http://www. emn.fr/{doue nce, sudholt} Abstract Computational reection is gaining interest in practical applications as witnessed by the use of reection in the J AV A programming environment and recent work on reective middleware. Reective systems offer many different reection programming interfaces, the so-called Meta- Objec t Protocols (MOPs) . Their design is subject to a number of constraints relati ng to, among others, expressive power, efciency and security properties. Since these constraints are different from one application to another, we should be able to easily provide specially-tailored MOPs. In this paper, we present a generic reication technique based on program transformation. It enables the selective reication of arbitrary parts of object-oriented metacircular interpreters. The program transformation can be applied to diffe rent interpre ter deniti ons. Each resulti ng reective implementation provides a different MOP directly derived from the original interpreter denition. Keywords: reection, OO languages, program transformation, language implementation 1 Introduction Computational reection, that is, the possibility of a software system to inspect and modify itself at runt ime, is gaini ng inter est in prac tica l appl icat ions: moder n soft ware freque ntly requires strong adaptability conditions to be met in order to t a heterogenous and evolving computing environment. Reection allows, for instance, host services to be determined dynamically and enables the modica- tion of interaction protocols at runtime. Concretely, the JAVA programming environment [java] relies heavily on the use of reection for the implementation of the JAV ABEANS component model and its remote metho d invoc ation mechanism. Furthermore, adap tabi lity is a prime requir ement of middle- ware systems and several groups are therefore doing research on reective middleware [coi99][bc00]. Reective systems offer many different reection programming interfaces, the so-called Meta- Object Protocols (MOPs) 1 . The design of such a MOP is subject to a number of constraints relating to, among others, expressive power , efciency and security properties. For instance, using reection Extended version. c 2001 Kluwer Academic Publishers. “Higher -Order and Symbolic Computation”, 14(1), 2001, to appear. 1 We use the term “MOP” in the sense of Kiczal es et al. [kic91 ] (page 1): “Meta objec t proto cols are interfaces to the language that give users the ability to incrementally modify the language’s behavior and implementation, as well as the ability to write programs within the language.” 1

Upload: uks

Post on 02-Apr-2018

221 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 1/26

A Generic Reification Technique for

Object-Oriented Reflective Languages 

Rémi Douence , Mario Südholt

École des Mines de Nantes

Département Informatique, 44307 Nantes cedex 3, Francehttp://www.emn.fr/{douence, sudholt}

Abstract

Computational reflection is gaining interest in practical applications as witnessed by the useof reflection in the JAVA programming environment and recent work on reflective middleware.Reflective systems offer many different reflection programming interfaces, the so-called Meta-Object Protocols (MOPs). Their design is subject to a number of constraints relating to, amongothers, expressive power, efficiency and security properties. Since these constraints are differentfrom one application to another, we should be able to easily provide specially-tailored MOPs.

In this paper, we present a generic reification technique based on program transformation.It enables the selective reification of arbitrary parts of object-oriented metacircular interpreters.The program transformation can be applied to different interpreter definitions. Each resulting

reflective implementation provides a different MOP directly derived from the original interpreterdefinition.

Keywords: reflection, OO languages, program transformation, language implementation

1 Introduction

Computational reflection, that is, the possibility of a software system to inspect and modify itself 

at runtime, is gaining interest in practical applications: modern software frequently requires strong

adaptability conditions to be met in order to fit a heterogenous and evolving computing environment.

Reflection allows, for instance, host services to be determined dynamically and enables the modifica-

tion of interaction protocols at runtime. Concretely, the JAVA programming environment [java] relies

heavily on the use of reflection for the implementation of the JAVABEANS component model and its

remote method invocation mechanism. Furthermore, adaptability is a prime requirement of middle-

ware systems and several groups are therefore doing research on reflective middleware [coi99][bc00].

Reflective systems offer many different reflection programming interfaces, the so-called Meta-

Object Protocols (MOPs)1. The design of such a MOP is subject to a number of constraints relating

to, among others, expressive power, efficiency and security properties. For instance, using reflection¡

Extended version. c¢

2001 Kluwer Academic Publishers. “Higher-Order and Symbolic Computation”, 14(1),

2001, to appear.1We use the term “MOP” in the sense of Kiczales et al. [kic91] (page 1): “Metaobject protocols are interfaces to the

language that give users the ability to incrementally modify the language’s behavior and implementation, as well as the

ability to write programs within the language.”

1

Page 2: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 2/26

for debugging purposes may require the MOP to provide access to the execution stack. However,

because of security concerns stack access must frequently be restricted: in JAVA, for example, it is not

allowed to modify the (untyped) stack because security properties essentially rely on type information.Since these constraints are different from one application to another, we should be able to provide

a specially-tailored MOP for a particular set of constraints. Moreover, the constraints may change dur-

ing the overall software life cycle. Hence, the development of such specially-tailored MOPs should

be a lightweight process. Traditional approaches to the development of MOPs do not meet this goal

instead each of them only provides a specific MOP which can hardly be modified (see the discussion

of related work in Section 9). Consider, for instance, a single-processor application which is to be

distributed. In this case, distinct tasks have to be performed on the message sending side and the

receiving side: for example, on the sender side local calls are replaced by remote ones (instead of 

relying on proxies) and on the receiver side incoming messages can be synchronized. Many existing

MOPs do not allow the behavior of message senders to be modified. Hence, such a distribution strat-

egy cannot be implemented using reflection in these systems. Some systems (see, for instance COD

A[aff95]) provide access to senders right from the start. Therefore, they can introduce an overhead for

local applications.

In this paper, we present a reification mechanism for object-oriented interpreters based on program

transformation techniques. We use a generic transformation which can be applied at compile time to

any class of a non-reflective interpreter definition. This mechanism can be used to transform different

subsets of a metacircular interpreter in order to generate increasingly reflective interpreters. It can

also be applied to different interpreter definitions in order to automatically get different reflective

interpreters. Each resulting reflective implementation provides a different MOP directly derived from

the original interpreter definition.

The paper is structured as follows: in Section 2, we briefly introduce Smith’s seminal reflective

towers upon which our work is based and we sketch the architecture of our transformational system.Section 3 provides an overview of a metacircular interpreter for JAVA. Our generic reification tech-

nique is formally defined and its application to the non-reflective interpreter is exemplified in Section

4. Section 5 is devoted to reflective programming: it details our reification technique at work by

presenting several applications. Section 6 complements Section 4 by presenting a few technicalities

postponed for the sake of readability. Section 7 discusses the correctness of the transformation and

sketches a formal correctness proof. Section 8 illustrates how a refined definition of the non-reflective

interpreter produces a more expressive reflective interpreter. Section 9 discusses related work. Fi-

nally, Section 10 concludes and discusses future work. Code occuring in the paper refers to a freely

available prototype implementation, called METAJ [metaj], which enables execution of the reflective

programming examples we present and provides a platform for experimentation with our technique.

2 Overview of the reification process

In our opinion, Smith’ definition of reflection [smi84] remains a key reference because of its clean

semantic foundation and generality. This paper proposes one method to transpose his technique into

the domain of object-oriented languages. In this section, we first introduce Smith-like reflection before

presenting the architecture of our reification method.

2

Page 3: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 3/26

Level 0

Level 1

Level 2

Level 3

 Interpreter

 Program Program

 Interpreter

 Interpreter Interpreter

 Interpreter’

 Program

 Interpreter

 Interpreter’’

 Interpreter’

 Program

Figure 1: Smith-like reflective towers

2.1 Smith-like Reflection

Smith’s seminal work on reflective 3-Lisp defines reflection with the notion of reflective towers. In

Figure 1, the left hand side tower shows a user-written (i.e. level 0) Program in a double-square boxand its Interpreter, which defines its operational semantics. A simple classic example of reflective pro-

gramming deals with the introduction of debugging traces. Trace generation requires the interpreter

to be modified, that is, two steps have to be performed at runtime: provide an accessible represen-

tation of the current interpreter and change this representation. Such a computation creates an extra

interpretation layer by means of a reification operator “reify,” so that the level 1 Interpreter  becomes

now part of the program: in the illustration, it is included in the double square box. We get a second

tower with three levels. The Program can now modify the standard semantics of the language defined

by the level 1 Interpreter  to get Interpreter ’ which generates traces during execution (see the third

tower). Finally, when a non-standard semantics Interpreter” of Interpreter ’ is required, a further extra

interpretation level can be introduced as illustrated by the fourth tower. The fourth tower would be

required, for example, to trace Interpreter’.On a more abstract level, Smith’s reflection model — as well as our reification technique — has

two essential properties: there is a potentially infinite tower of reflective interpreters and the interpreter

at level   interprets the actual code of the interpreter at level ¢ ¡ £ .

2.2 Making object-oriented interpreters reflective

In order to get a first intuition of our reification technique, consider the following simple example of 

how we intend reflection to be used: color information (represented by the class Color) should be

added to pairs at runtime. Using reflection, we could dynamically modify the inheritance graph such

that Pair inherits from Color. This can be achieved by

// Pair extends Object( ¥ Pair).extendsLink = Color;

// Pair extends Color

where¥

is a reification operator. The application of the reification operator to an expression yields

an accessible representation of the value denoted by the expression. In this example, the expression

Pair denotes the corresponding Class object, say c, in the interpreter’s memory (see Figure 7).

¥ Pair returns an instance (say i), i.e. an object of type Instance, in the interpreter’s memory

which represents c and which can be inspected and modified. The default superclass of  Pair is

replaced by Color by assigning the field extendsLink. From now on, newly instantiated pairs

contain color information.

It is crucial to our approach that the reified representation i is based on the definition of  c. This

3

Page 4: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 4/26

...

...

Parser

Java.jjt

Runtime System

ExpAssign.java

Reflective Interpreter

Reflective_Prog.java

Java2ExpVisitor.java

...

Parser

Java.jjt

Runtime System

ExpAssign.java

Non-Reflective Interpreter

Prog.java

...

Java2ExpVisitor.java

Instance.java

BaseClass.java

Class.java

Class.java

Instance.java

 generation of reflective interpreter

 pr  o gr  a m  t  r  a n s f   or m a  t  i   on

Class.java

Figure 2: System architecture

is achieved by the system architecture shown in Figure 2. Our non-reflective JAVA interpreter (repre-

sented by the box at the top) takes a non-reflective program Prog.java as input. This program is

parsed into a syntax tree and evaluated. According to the required reflective capabilities, the language

designer2 transforms a subset of the classes of the non-reflective interpreter. Basically, this transfor-

mation generates two classes for each original class. In our example, the file Class.java, which

represents classes in the non-reflective interpreter, becomes BaseClass.java and a different ver-

sion of Class.java in the reflective one.

The reflective interpreter relies on the non-reflective interpreter in order to build levels of the

reflective tower. This is the core issue of our approach: the tower levels shown in Figure 1 are

effectively built at runtime on the basis of the (verbatim) definition of the non-reflective interpreter  as

in Smith’s model. This is why the original definition of  Class.java is an input of the reflective

interpreter in Figure 2. So, the behavior of the reflective interpreter is derived from the non-reflective

one. Furthermore, our approach is selective and complete because the transformation is applicable to

any class of the non-reflective interpreter definition.

METAJ implements one version of this system architecture . Its parser has been implemented

by means of JAVACC and JJTREE (versions 0.8pre2 and 0.3pre6, respectively). METAJ itself is

operational with the JDK versions 1.1.6 and 1.2.

3 A simple non-reflective interpreter

We have implemented a non-reflective metacircular interpreter for a subset of JAVA, which provides

support for all essential object-oriented and imperative features, such as classes, objects, fields, meth-

ods, local variables and assignment statements. (We did not implement features such as some primitive

2Note that building a reflective interpreter by transformation and writing reflective programs are two different tasks: the

former is performed by language designers and the latter by application programmers.

4

Page 5: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 5/26

class ExpId extends Exp {

private String id;

ExpId(String id) { this.id = id; }

Data eval(Environment localE) {return localE.lookup(this.id);

}

}

Figure 3: Class ExpId

class ExpAssign extends Exp {

private Exp lhs;

private Exp rhs;

ExpAssign(Exp lhs, Exp rhs) {

this.lhs = lhs; this.rhs = rhs;

}

Data eval(Environment localE) {

Data d1 = this.rhs.eval(localE);

Data d2 = this.lhs.eval(localE);

d2.write(d1.read());

return d2;

}

}

Figure 4: Class ExpAssign

types or loop constructs; all of these could be integrated and reified similarly.)

JAVA programs are represented as abstract syntax trees the nodes of which denote JAVA’s syn-

tactic constructs and are implemented by corresponding classes. For example, variables, assignment

statement, method call, and class instantiation expressions are respectively encoded by the classes

ExpId, ExpAssign, ExpMethod and ExpNew. All of these classes define an evaluation method

Data eval(Environment localE) that takes the values of local variables in localE and

returns the value of the expression (wrapped in a Data object).

In particular, ExpId (see Figure 3) holds the name of a variable and its evaluation method yields

the value currently associated to the variable in the local environment. An ExpAssign node (see

Figure 4) stores the two subexpressions of an assignment. Its evaluation method evaluates the location

of the right-hand side expression, followed by the value represented by the left-hand side expression

and finally performs the assignment. ExpMethod (see Figure 5) represents a method call with a re-

ceiver expression (exp), a method name (methodId) and its argument expressions (args). Method

call evaluation proceeds by evaluating the receiver, constructing an environment from the argument

values, looking up the method definition and applying it. ExpNew (see Figure 6) encodes the class

name (classId) and constructor argument expressions. Its evaluation fetches the class definition

from the global environment, instantiates it and possibly calls the constructor.

As suggested before, the interpreter defines a few other classes to provide a runtime system and

implement an operational semantics. For example, the class Class (see Figure 7) represents classes

by a reference to a superclass (extendsLink), a list of fields (dataList) and a list of methods

(methodList). It provides methods for instantiating the class (instantiate()), accessing the

list of methods including those in super classes (methodList()), etc. Methods are represented by

5

Page 6: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 6/26

class ExpMethod extends Exp {

private Exp exp; // receiverprivate String methodId; // method name

private ExpList args; // arguments

ExpMethod(Exp exp, String methodId, ExpList args) {

this.exp = exp; this.methodId = methodId; this.args = args;

}

Data eval(Environment localE) {

// evaluate the lhs (receiver)

Instance i = (Instance) this.exp.eval(localE).read();

// evaluate the arguments to get a new local environment

Environment argsE = Environment.Empty;

this.args.eval(localE, argsE);

// lookup and apply method

Method m = i.lookupMethod(this.methodId);

return m.apply(argsE, i);

}

}

Figure 5: Class ExpMethod

class ExpNew extends Exp {

private String classId; // class name

private ExpList args; // constructor arguments

ExpNew(String classId, ExpList args) {

this.classId = classId; this.args = args;

}

Data eval(Environment localE) {

// get the Class and create an Instance

Class aClass = (Class) (Main.globalE.lookup(this.classId).read());

Instance i = aClass.instantiate();

// call non default constructor if it exists

if (i.getInstanceLink().methodList().member(this.classId).booleanValue()) {

Environment argsE = Environment.Empty;

this.args.eval(localE,argsE);// lookup and apply method

Method m = i.lookupMethod(this.classId);

m.apply(argsE, i);

}

return new Data(i);

}

}

Figure 6: Class ExpNew

6

Page 7: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 7/26

class Class {

Class extendsLink; // superclass

DataList dataList; // field list

MethodList methodList;

Class(Class eL, DataList dL, MethodList mL) {

this.extendsLink = eL;

this.dataList = dL;

this.methodList = mL;

}

Class getExtendsLink() { return this.extendsLink; }

// implementation of Java’s ’new’ operator

Instance instantiate() { ... }// compute complete method list (incl. superclasses)

MethodList methodList() { ... }

...

}

Figure 7: Class Class

class Method {

private StringList args; // parameter names

private Exp body; // method body

Method(StringList args, Exp body) {

this.args = args; this.body = body;

}

Data apply(Environment argsE, Instance i) {

// name each argument

argsE.zipWith(this.args);argsE.add("this", new Data(i));

// eval the body definition of the method

return this.body.eval(argsE);

}

}

Figure 8: Class Method

7

Page 8: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 8/26

the class Method (see Figure 8) by means of a list of argument names ( args) and a body expression

(body). Its method apply() binds argument names to values including this and evaluates the

body. Other classes include Instance (contains a reference instanceLink to its class and alist of field values; provides field lookup and method lookup), MethodList, Data (implements

mutable memory cells such as fields), DataList, Environment (maps identifiers to values), etc.

The architecture of the interpreter follows the standard design for object-oriented interpreters as

presented in Gamma et al. [ghjv95] by the Interpreter  design pattern. Instantiating this design pattern,

the following correspondences hold: their ‘Client’ is our interpreter’s main() method, their meth-

ods ‘Interpret(Context)’ is our eval(Environment). The reification technique described in this

paper is applicable to other interpreters having such an architecture. Note that these interpreters may

implement many different runtime systems.

4 Generic reification by code transformation

In this section, we give an overview of our generic reification scheme for the class Class and for-

mally define the underlying program transformation. (For the sake of readability, we postpone the

discussion of a few technicalities to Section 6.) Then, we apply it in detail to the class Instance.

4.1 Overview of the generic reification scheme

Reification of an object should not change the semantics of that object but change its representation

and provide access to the changed representation. For example, it is not possible to modify the su-

perclass of a class at runtime in our non-reflective interpreter (although a reference representing the

inheritance relation exists in the memory of the underlying implementation). The reified representa-

tion of a class provides access to this reference. Once the internal representation has been exposed,access to this structure allows the semantics of the program to be changed (e.g. by means of dynamic

class changes). Note that this form of structural reification of the interpreter memory subsumes the

traditional notions of structural and behavioral reflection.

For illustration purposes, consider a class Pair with two fields fst and snd which is imple-

mented in the interpreter memory by a Class (from here on, C denotes an instance of the class C).

In order to reify Pair, we choose Class to be reifiable. Basically, a reifiable entity can have two

different representations as exemplified in Figure 9: either a base representation or a reified represen-

tation. Since reification of any object does not change its behavior, the object should provide the same

method interface in both representations. This common interface is implemented using a dispatch

object3: the Class denoted by Pair.

The dispatch object points to the currently active representation: either the base representation(BaseClass in Figure 9a) or the reified representation (Instance denoted by ¥ Pair in Figure

9b). The dispatch object provides a method reify() (triggered by ¥ ) to switch from the base

representation to the reified one: a call to reify() creates a new tower level. The dispatch object

executes incoming method calls according to the active representation: when the base representation

is active, the dispatch simply delegates incoming method calls to it. When the reified representation

is active, the dispatch object interprets the method call.

Whether an object is accessed through its dispatch object or through its reified representation is

irrelevant, that is, modification of the object through the access path Pair is visible through the other

access path ¥ Pair. (This property is commonly referred to as the causal connection between levels.)

3The dispatch technique is close to the bridge and state patterns introduced in Gamma et al. [ghjv95].

8

Page 9: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 9/26

denotes

methodList = Pair()

extendLink = Object

dataList = fst, snd

BaseClass

dispatch object 

Class

isReified = false

representation =

dispatch object 

Class

isReified = true

representation =

Instance

dataList = dataList,

methodList, extendLink

instanceLink = Class

X X

active representationinstance of 

a) before ∆ b) after ∆Pair Pair

Pair Pair Pair∆

diff erent

representations

Figure 9: Before and after reification of the class Pair

Obviously, the two paths provide different interfaces. Consider, for example, the problem of keeping

track of the number of Pair instances using a static field countInstances: this field could be ac-

cessed either by Pair.countInstances or by ( ¥ ( ¥ Pair).staticDataList).lookup

("countInstances")4. In the last expression, the outermost reification operation is necessary in

order to call lookup() on a data list object (cf. the fourth item below).

In order to conclude this overview, we briefly mention other important properties of our reification

scheme:

  Since reflection provides objects representing internal structure for use in user-level programs,

every reification operation returns an Instance (e.g. the one in Figure 9b). This implies that

reification of reified entities requires that Instances are reifiable.

 

¥ Exp yields an accessible representation of the value denoted by Exp (i.e. an object in the

interpreter’s memory, such as Class, Instance, Method)5

.  References from dispatch objects to their active representations cannot be accessed by user

programs. Only a call to the reification operator may modify these references. This ensures that

the tower structure cannot be messed up by user programs.

  The scope of the reification process is limited to individual objects in the interpreter’s memory.

For example, the reification of a class does not reify its list of methods methodList nor its

superclass. So, three categories of objects coexist at runtime: reified objects, non-reified (but

4METAJ does not allow static fields but could be extended easily to deal with such examples.5 ¡ is a strict operator. A syntax extension would be necessary to reify the expression (e.g. the AST representing 1+4)

rather than the value denoted by the expression (e.g. the integer 5).

9

Page 10: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 10/26

class Name {

Type 

¡

field £ ¡

;

...

Type

 ¤

field 

 ¤

;

 Name(Type ¡

arg ¡

, ..., Type 

¤

arg 

¤

) { body }

Type §

¡

method §

¡

(Type §

¡¡

arg§

¡¡

, ..., Type §

¡

arg §

¡

) { body §

¡

}

...

Type § method §

(Type §

¡

arg §

¡

, ..., Type§ arg§

) { body § }

}

Figure 10: Original class definition

reifiable) ones and non-reifiable ones. If a program accesses an object o through a reified one,

the use of  o is restricted exactly as in the non-reflective case. ¥ Pair.extendsLink, for

example, references a Class representing the superclass of  Pair. Therefore, the only valid

operations on this reference are new ( ¥ Pair.extendsLink)()6 as well as accesses to

static fields and members of this class. If the structure or behavior of the superclass is to be

changed, it must be reified first. This implies that accesses to non-reifiable objects through

reified ones are safe.

4.2 Formal definition of the generic reification scheme

Based on the implementation technique outlined above, our generic reification scheme is an automatic

program transformation which can be applied to an arbitrary class, called Name in the followingdefinition, of the original interpreter. As shown in Figure 10, such classes consist of a number of fields

and methods and must have a constructor with arguments for all of their fields. The transformation of 

a set of classes has time and space complexity linear in the number of classes.

The transformation consists of two main steps:

1. Introduce the class BaseName (see Figure 11) which defines the base representation of the

original class Name. This class is very similar to the original class Name.

2. Redefine the class Name (see Figure 12) such that it implements the corresponding dispatch

object. This class provides the same method interface as the original class Name and imple-

ments a methodreify()

which creates the reified representation and switches from the baserepresentation to the reified one.

Figure 11 shows the generated base class. (In the figures of this section, we use different style

conventions for verbatim text, schema variables and [text substitutions].) Ba-

sically, the original class is renamed and a field referent is added. Remember that a reifiable entity

is implemented by a dispatch object that points to the current representation. The referent field,

which is initialized in the constructor and points back from the representation to the dispatch object,

is mandatory to distinguish the dispatch object and the representation: if this is not used to access

6The current parser of METAJ does not allow such an expression: new requires a class identifier. However, the parser

could be easily extended to deal with such expressions and we allow this notation in this paper.

10

Page 11: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 11/26

class BaseName {

Type 

¡

field  

¡

;

...

Type

 ¤

field 

 ¤

;

 Name referent;

BaseName(Type 

¡

arg 

¡

, ..., Type 

¤

arg 

¤

, Name referent) {

 body

this.referent = referent;

}

Type §

¡

method §

¡

(Type §

¡¡

arg§

¡¡

, ..., Type §

¡

arg §

¡

) {

 body§

¡

[ this.referent / this(~.) ]

}

...

Type § method §

(Type §

¡

arg §

¡

, ..., Type§ arg§ ) {

 body§ [ this.referent / this(~.) ]

}

}

Figure 11: Generated base class

fields or methods in the base class it should denote the dispatch object 7. In the transformation, this

is implemented by substituting this(~.) (matching the keyword  this followed by anything but adot) by this.referent.

The generated dispatch class, shown in Figure 12, has two fields: representation that points

to either the base representation or the reified representation, and a boolean field isReified that

discriminates the active representation. Its constructor creates a base representation for the object.

The methods method   ¡

have the same signature as their original version. When the base repre-

sentation is active (i.e. isReified is false), the method call is delegated to the base representation.

When the reified representation is active (i.e. isReified is true), the method call is interpreted:

the corresponding call expression is parsed (Parser.java2Exp()), a local environment is built

(argsE.add()) from the method arguments and the field representation of the dispatch ob-

 ject and the method call is evaluated (eval()). Note that for the sake of clarity, this code is inten-

tionally naive. The actual implemented version could be optimized: for example, the call to the parsercould be replaced by the corresponding syntax tree.

The method reify() builds a reified representation of the base representation by evaluating a

new-expression. The corresponding class is cloned in order to build a new tower level. So, every

reified object has its own copy of a Class. This way, the behavior of each reified object can be spe-

cialized independently. If sharing is required the application programmer can achieve it by explicitly

manipulating references. Finally, the reified representation is installed as the current representation

and a reference to it is returned. A series of experiments led us to this sharing strategy. A previous

version of the transformation did not clone the class. This sharing led to cycling dependency relation-

ships and reflective overlap after reification: in particular, reification of the class Class introduced

7This is a typical problem of wrapper-based techniques that introduce two different identities for an object.

11

Page 12: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 12/26

class Name {

Object representation;

boolean isReified;

 Name(Type 

¡

arg 

¡

, ..., Type 

¤

arg 

¤

) {

this.isReified = false;

this.representation = new BaseName(arg 

¡

, ..., arg 

¤

, this);

}

Type §

¡

method §

¡

(Type §

¡¡

arg§

¡¡

, ..., Type §

¡

arg §

¡

) {

if (this.isReified) {

Exp exp = Parser.java2Exp(

"reifiedRep. method §¡

(arg§

¡¡

,...,arg §

¡

)");

Environment argsE = Environment.Empty;

argsE.add("reifiedRep", this.representation);

argsE.add("arg §

¡

", arg §

¡

);

...

argsE.add("arg §

¡¡

", arg §

¡¡

);

Data result = exp.eval(argsE);

return result.read();

} else

return (BaseName)this.representation. method §

¡

(arg §

¡¡

, ... , arg §

¡

);

}

...

Instance reify() {

if (!this.isReified) {

Class aClass = Main.globalE.lookup(" Name").clone();

Exp exp = Parser.java2Exp("new aClass(baseRep_field  

¡

,...,

baseRep _field  ¤

,

Environment argsE = Environment.Empty;

argsE.add("baseRep_ field  

¡

", this.representation.field  

¡

);

...

argsE.add("baseRep_ field  

¤

", this.representation.field  

¤

);

argsE.add("aClass", aClass);

this.isReified = true;

this.representation = exp.eval(argsE).read();

}

return (Instance)this.representation;

}

}

Figure 12: Generated dispatch class

12

Page 13: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 13/26

non-termination. Alternatively, we experimented with one copy of each class per level but in this case

the reification (without modification) of an object could already change its behavior.

This generic reification technique is based on only two assumptions:

1. Each syntactic construct is represented by an appropriate expression during interpreter execu-

tion. We assume that all of these expressions can be evaluated using the method eval(argsE)

where argsE contains the current environment, i.e. the values of the free variables in the cur-

rent expression.

2. We assume that the textual definitions of all reifiable classes have been parsed at interpreter cre-

ation time and that they are stored as Class objects in the global environment Main.globalE.

These objects have to be cloneable.

This way, reify() creates an extra interpreter layer based on the actual interpreter definition.

Note that these simple assumptions and the formal definition enable the transformation to be per-formed automatically.

In Java, the operator new returns an object (i.e. an Instance). Therefore, in order to let the user

build other runtime entities than Instances, such as Classes and Methods, we provide a family

of deification8 operators, one for each of these entities. These operators are the inverse of the generic

reification operator. For example, in the reflective program (where   

Class denotes the deification

operator for classes):

( ¥ Pair).extendsLink =   

Class(new Class(...));

the right-hand side expression returns a Class dispatch object in front of the Instance created by

new. Note that the deification operators — while functionally inverting the reification operation —

do not change the representation of an object “back” to its unreified structure (e.g. to a BaseClass

in the case of classes).The dispatch objects engender the structure of the reflective tower; their implementation is not

accessible to the user. In particular, the reification operator and the deification operators encapsulate

the fields representation and isReified of dispatch objects as well as the field referent

from the base class. So, user programs cannot arbitrarily change the tower structure. However, the

user or a type system to be developed should avoid the creation of meaningless structures, such as   

Class(new Method(...)).

4.3 Example: making the class Instance reifiable

To illustrate the definition of the transformation, we apply it to the class Instance (see Figure 14),

which is used in the examples of reflective programming in the next section. This class implementsobjects in the interpreter. For example, a pair object with two fields fst and snd is implemented

by an Instance the field dataList of which contains two memory cells labelled fst and snd.

Its field instanceLink points to a Class containing the methods of the class Pair. The method

lookupData() is called whenever a field of  pair is accessed. (For the sake of conciseness, we

did not show the other methods of Instance, such as lookupMethod().)

The application of the transformation defined above to Instance yields the two classes Ba-

seInstance (see Figure 15) and the dispatch class Instance (see Figure 16). Now, pair is

implemented by a dispatching Instance as shown in Figure 13. Its default unreified representation

is a BaseInstance (say¡ ¢

) whose dataList field contains the fields labelled fst and snd (see

8We prefer the term ‘deification’ [iyl95] to the equivalent terms ‘reflection’ [wf88] and ‘absorption’ [meu98].

13

Page 14: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 14/26

dispatch object 

Instance

representation =

isReified = false

dispatch object 

Instance

representation =

isReified = false

denotesX X active representationinstance of 

a) before b) after

BaseInstance

dataList = fst, snd

instanceLink = Pair

∆ ∆

BaseInstance

dataList = dataList,

instanceLink

instanceLink = Instance

pair pair

 d i f f e r e n t

 r e p r e s e n t a

 t i o n s

pairpair pair∆

dispatch object 

Instance

representation =

isReified = true

Figure 13: Before and after reification of the object pair

class Instance {

public Class instanceLink; // ref. to Class

public DataList dataList; // field list

Instance(Class instanceLink, DataList dataList) {

this.instanceLink = instanceLink;this.dataList = dataList;

}

// field access

Data lookupData(String name) {

return this.dataList.lookup(name);

}

...

}

Figure 14: Original class Instance

14

Page 15: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 15/26

class BaseInstance {

Class instanceLink;

DataList dataList;

Instance referent;BaseInstance (Class instanceLink, DataList dataList,

Instance referent) {

this.instanceLink = instanceLink;

this.dataList = dataList;

this.referent = referent;

}

Data lookupData(String name) {

return this.dataList.lookup(name);

}

...

}

Figure 15: Class BaseInstance

Figure 13a). Once pair has been reified (see Figure 13b), it is represented  by an Instance which

points to a BaseInstance (say¡

  ). Note that in contrast to the reification of classes shown in Fig-

ure 9, the reified representation of an instance is reifiable (because it is an instance itself; hence, the

second dispatching Instance in Figure 13b). Since the reification is based on the actual definition of 

the original Instance, the dataList of ¡

  contains the three fields instanceLink, dataList

(itself containing fst and snd) and referent. The definition of the method lookupData() in

the dispatch object calls the method lookupData()of ¡ ¢

as long as pair is not reified. Once it is

reified, the definition of lookupData() of Instance is interpreted .In order to prove the feasibility of our approach, we applied this reification technique to different

classes defining object-oriented features of our JAVA interpreter resulting in the prototype METAJ. The

imperative features of the non-reflective interpreter can be tackled analogously. This way we could,

for example, redefine the sequentialization operator ‘;’ in order to count the number of execution steps

in a given method (say m). One way to achieve this is by reification of occurrences of  ExpS in reified

m and dynamically changing their classes by a class performing profiling within the eval() method.

Another solution would be to replace ExpS nodes in reified m by nodes including profiling.

5 Reflective Programming

In this section, we express several classic examples of reflective programming in our framework.

These detailed examples of our reflective interpreter at work should help the reader’s understanding

of the system’s working.

The examples highlight an important feature of our design: since our reification scheme relies on

the original interpreter definition, the meta-object protocol of the corresponding reflective interpreter

(i.e. the interface of a reflective system) is quite easy to apprehend. It consists of a few classes which

are reifiable in METAJ, the reification operator ¥ and the deification operators   

.

In Figure 17 the class Pair is defined, and in main() a new instance pair is created. In the

interpreter, the object pair is represented by an Instance (see Figure 13a). Our generic reification

method provides access to a representation of this Instance which we name metaPair (denoted

by ¥ pair in Figure 13b). The most basic use of reflection in object-oriented languages consists in

15

Page 16: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 16/26

class Instance {

Object representation;

boolean isReified ;

Instance(Class instanceLink, DataList dataList) {

this.isReified = false;

this.representation =

new BaseInstance(instanceLink, dataList, this);

}

Data lookupData(String name) {

if (this.isReified) {

// interpret lookup method callExp exp = Parser.java2Exp("reifiedRep.lookupData(name)");

// pass already evaluated values

Environment argsE = Environment.Empty;

argsE.add("name", name);

argsE.add("reifiedRep", this.representation);

Data result = exp.eval(argsE);

// unpack result

return (Data)result.read();

} else

return ((BaseInstance)this.representation).lookupData(name);

}

...

Data reify() {

if (!this.isReified) {

// copy the base class BaseInstance

Class aClass = Main.globalE.lookup("Instance").clone();

// create and initialize new representation

Exp exp = Parser.java2Exp("new aClass(baseRep_instanceLink,

baseRep_dataList)");

Environment argsE = Environment.Empty;

argsE.add("baseRep_instanceLink", this.representation.instanceLink);

argsE.add("baseRep_dataList", this.representation.dataList);

argsE.add("aClass", aClass);

this.isReified = true;

this.representation = exp.eval(argsE).read();}

return new Data(this.representation);

}

}

Figure 16: Dispatch class Instance

16

Page 17: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 17/26

class Pair {String fst;

String snd;

Pair(String fst, String snd) {

this.fst = fst;

this.snd = snd;

}

}

class PrintablePair extends Pair {

String toString() {

return "(" + this.fst + "," + this.snd + ")";

}}

class InstanceWithTrace extends Instance {

Method lookupMethod(String name) {

// trace method-called 

System.out.println("method called: " + name);

return this.instanceLink.methodList().lookup(name);

}

}

class Main {

void main() {

Pair pair = new Pair("1", "2");

// 1 - invariance under reification

Instance metaPair =   

pair;

System.out.println("pair.fst: " + pair.fst);

// 2 - introspection: test existence of a super class

Class metaClass =   

Pair;

if (metaClass.getExtendsLink() == null)

System.out.println("Class Pair has no superclass");

// 3 - intercession: dynamic class change

metaPair.instanceLink = PrintablePair;

System.out.println("pair.toString(): " + pair.toString());

// 4 - intercession: change method-call semantics

Instance metaMetaPair =   

metaPair;

metaMetaPair.setInstanceLink(InstanceWithTrace);

System.out.println("pair.toString(): " + pair.toString());// 5 - instance and class deification

System.out.println((¡ InstancemetaPair).fst);

reify(PrintablePair).extendsLink =¡ ClassmetaClass;

System.out.println("pair.toString(): " + pair.toString());

}

}

Figure 17: Examples of Reflective Programming

17

Page 18: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 18/26

reifying an object: changing the internal representation without modifying its behavior (see Example

1). Another simple use is introspection. Let us consider the problem of testing the existence of a super

class of a given class. In Example 2, the class Pair (represented by a Class in the interpreter) isreified which enables its method getExtendsLink() to be called.

In METAJ, reflective programming is not limited to introspection, but the internal state of the

interpreter can also be modified (aka intercession). The third example in main() shows how the

behavior of an instance can be modified by changing its class dynamically. Imagine that we would

like to print pairs using a method called toString(). We define a class PrintablePair which

extends the original class Pair and implements a method toString(). A pair can then be made

printable by dynamically changing its class from Pair to PrintablePair (remember that the

field instanceLink of  Instance holds the class of the represented instance, see Figure 15).

Afterwards the object pair understands the method toString().

The fourth example deals with method call tracing for debugging purposes. The class Instance

of the interpreter defines the method Method lookupMethod(String name) that returns theeffective method to be called within the inheritance hierarchy. In our interpreter each lookup-

Method() is followed by an apply(). Thus, method call tracing can be introduced by defining

a class InstanceWithTrace which specializes the class Instance of the interpreter such that

its method lookupMethod() prints the name of its parameter. In order to install the tracing of 

method calls of the instance pair, its standard behavior defined in the interpreter by the class In-

stance (note that this class can be accessed because the interpreter definition is an integral part of 

the reflective system built on top of the reflective interpreter) is replaced by InstanceWithTrace.

Reification of pair provides access to an Instance whose field instanceLink denotes the class

Pair. A sequence of two reification operations on pair provides access to an Instance whose

instanceLink denotes the class Instance. This link can then be set to the class Instance-

WithTrace. A method call of the object pair then prints the name of the method. Therefore,"toString" is printed by our third example. Finally, note that our tower-based reflection scheme

makes it easy to trace the tracing code if required because any number of levels may be created by a

sequence of calls to ¥ .

The fifth (rather artificial) example illustrates deification by deifying metaPair and meta-

Class in order to create an instance and a class at the base level. After deification of the reified

representation metaPair we show that base-level operations can be performed on the resulting ob-

 ject. In the case of class deification, we restore the original class of  pair.

More advanced examples that illustrate our approach rely on the capacity to reify arbitrary parts

of the underlying interpreter. As discussed in Section 4.3, the reification of  ExpS allows the behavior

of the sequence operator ‘;’ to be changed. This way, we could, for instance, stop program execution

at every statement for debugging purposes or handle numeric overflow exceptions by re-executingthe current statement block with higher-precision data representations. Furthermore, reification of the

control stack would allow Java’s try/catch-mecanism for exception handling to be extended by a retry

variant.

6 The nuts and bolts of generic reification

Section 4 presents the essential parts of the generic reification mechanism. However, the actual imple-

mentation of a full-fledged reflective system requires several intricacies to be handled. In the current

section, we motivate the problems which must be handled and sketch the solution we developed. For

an in-depth understanding of these technicalities we refer the reader to the METAJ source code.

18

Page 19: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 19/26

class ExpId extends Exp {

// same fields and constructor as in

Data eval_original(Environment localE) {// same definition as eval in

}

Data eval(Environment localE) {

if (!localE.member("#meta_level").booleanValue())

return this.eval_original(localE);

else {

if (this.id.equals("this"))

return new Data(((Instance) localE.lookup("this").read()).referent);

else return eval_original(localE);

}

}

Figure 18: Class ExpId

class ExpMethod extends Exp {

...

Data eval(Environment localE) {

Instance i = null;

if (!localE.member("#meta_level").booleanValue())

return this.eval_original(localE);

else {

// evaluate the lhs (object part)

Object o = this.exp.eval(localE).read();if (o instanceof Reifiable && ((Reifiable) o).getIsReified()) {

// evaluate the receiver

i = ((Reifiable) o).reify();

// evaluate the arguments to get a new local environment

Environment argsE = new Environment(null, null, null);

argsE.add("#meta_level", new Data(null));

this.args.eval(localE, argsE);

// lookup the method and apply it

Method m = i.lookupMethod(this.methodId);

return m.apply(argsE, i);

} else {

if ((o instanceof DataList) && this.methodId.equals(lookup)) {

Environment argsE = Environment.Empty;

this.args.eval(localE, argsE);

return new Data(((DataList) o)

.lookup((String) argsE.getData().read()));

} else ... // other delegation cases

}

}

}

}

Figure 19: Class ExpMethod

19

Page 20: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 20/26

First, in the reflective interpreter a reified object is represented by a dispatch object and  a reified

representation. So, basically a reified object has two different identities. With our technique, this is

bound to the representation rather than the dispatch object by parsing the expression "reifiedRep.method 

 

(arg 

   

,\dots,arg 

  ¢

)" in the dispatch object (see Figure 12). However, if a state-

ment return this is to be interpreted, this should denote the dispatch object. Otherwise, user-

level programs could expose the reified representations. The interpreter class ExpId is in charge

of identifier evaluation (including this) and has therefore to be modified to account for this be-

havior. In Figure 18 the method eval() distinguishes two cases by means of the environment-tag

#meta_level.9 First, interpretation has been initiated by the interpreter’s entry point and non-

reflective evaluation is necessary. Second, interpretation has been initiated by a dispatch object and

reflective interpretation is required. In the first case eval_original() is called: this method has

the same definition as eval() in the non-reflective interpreter. In the second case if the identifier is

this, the dispatch object of the current representation is returned. Remember that the field refer-

ent points back from the base representation to the dispatch object, the same mechanism is used tolink the reified representation to the dispatch object. This field must be set by the methods reify(),

so the class Instance has to provide such a field10.

Second, remember that the scope of reification is limited to a single object in the interpreter

memory. This means interpretation involves reified and non-reified objects. For example, the reifi-

cation of an Instance does not reify neither its field list dataList nor its class denoted by in-

stanceLink. In particular, once an Instance has been reified, the interpretation of its method

lookupData (repeated from Figure 14):

Data lookupData(String name){return this.dataList.lookup(name);}

requires this.dataList to be interpreted and the call lookup(name) to be delegated because

this.dataList denotes a non-reifiable object. In abstract terms, a dispatch object introduces an

interpretation layer (a call to eval()) and this layer has to be eliminated when the scope of thecurrent (reified) object is left. This scheme is implemented in ExpMethod.eval() (see Figure

19).

Because of these two problems, the methods ExpData.eval() and ExpNew.eval() have

to be modified similarly. This means that our reification scheme cannot be applied to the four classes

ExpId, ExpMethod, ExpData, ExpNew11. However, our method provides much expressive

power: these restrictions fix the relationship between certain syntactic constructs and the runtime

system, but the runtime mechanisms themselves can still be modified as exemplified in Section 5. In

order to weaken this restriction, we designed and implemented a variant 12 of our reification scheme

that does not require ExpId and ExpData to be modified. Unfortunately, this advantage comes at a

price: the field referent can be exposed and modified by reification in this case.

7 Discussion of the correctness of the transformation

A complete treatment of the correctness of our technique is beyond the scope of this paper. However,

in this section we discuss very briefly work related to semantics of reflective systems and sketch a few

essential properties constituting a skeleton for a formal correctness proof of our technique.

9The dispatch objects insert this tag into the local environment.10For the sake of simplicity, the code shown in Figures 12 and 16 does not mention the field referent.11The restriction that all parts of a reflective system cannot be reified seem to be inherent to reflection [wf88].12This variant is also bundled in the METAJ distribution.

20

Page 21: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 21/26

Semantics of reflective programming systems is a complex research domain. Almost all of the

existing body of research work in this domain is about reflection in functional programming languages

[wf88][dm88][mul92][mf93]. Even in this context, foundational problems still exist. For example, itseems impossible to give a clean semantics which avoids introducing non-reifiable components [wf88]

and logics of programming languages must be considerably weakened in order to obtain a consistent

theory of reification [mul92]. One of the very few formal studies of reflection in a non-functional

setting has been done by Malenfant et al. [mdc96]. This work deals with reflection in prototype-

based languages and focuses on the lookup() ; apply() MOP formalized by means of rewriting

systems. This approach is thus too restricted to serve as a basis for our correctness concerns. In

general, semantic accounts of imperative languages are more difficult to define than in the functional

case. In particular, the transposition of the results obtained in the functional case to our approach

requires further work. We anticipate that this should be simpler in a transformational setting such as

ours than for arbitrary reflective imperative systems.

In order to prove the correctness of our scheme, the basic property to satisfy would be equivalencebetween a non-reflective interpreter¢ ¡ £

and a reflective interpreter¥ ¦ ¨ ¨

   ¡ £

generated by applying our

transformation to   ¡ £

, i.e.

! # $ % & '  

¡ £ ) 1 3

¥ ¦ ¨ ¨

  ¡ £

) 1

Since the transformation¥ ¦

is operating on individual classes, this property can be tackled by

establishing an equivalence between an arbitrary class (say c) of the non-reflective interpreter and its

transformed counterpart. Essentially, the transformation introduces an extra interpretation layer into

the evaluation of the methods of c. Programs and their interpretations introduced by transformation

satisfy the property

java2Exp("

").eval(Environment.Empty.add("6

$

",6

$ 8

)).read()3

¨ 6

$ @

6

$ 8

This property can be proven by induction on the structure of the AST representation of 

. (Note

that the formulation of this property is intentionally simplistic and should be parameterized with

contextual information, such as a global environment and a store.) It can be applied to the dispatch

classes (see Figure 12) to fold interpreting code into delegating code. When the then-branches of 

dispatching methods are rewritten using the property from left to right, the then-branches equal the

corresponding else-branches. Henceforth, the conditionals become useless and the dispatch objects

become simple indirections that can be suppressed. In the case of the method reify(), the rewriting

leads to the expression new Name(...) that creates a copy of the non-reified representation.

Finally, we strongly believe our transformation is type-safe (although we did not formally prove

this): every well-typed interpreter is transformed into a well-typed reflective interpreter. Obviously,

wrongly-typed user programs may crash the non-reflective interpreter. In the same way, some reflec-

tive programs may crash the reflective interpreter, for instance by confusing reflective levels or trying

to access a field which has been previously suppressed using intercession. Specialized type systems

and static analysis methods for safe reflective programming should be developed.

8 Generating alternative metaobject protocols

We have already mentioned that each set of reified classes along with their definitions determines a

MOP of its own. We think that this is a key property of our approach because it provides a basis for the

systematic development of specially-tailored MOPs. In this section, we modify the message-sending

part of the non-reflective interpreter in order to provide a finer-grained MOP which distinguishes the

sender and the receiver of a message.

21

Page 22: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 22/26

class Instance {

...

// add two new methods

Data send(Msg msg) {

return msg.to.receive(msg);

}

Data receive(Msg msg) {

return msg.to.lookupMethod(msg.methodId)

.apply(msg.argsE, msg.to);

}

}

class ExpMethod extends Exp {

...

Data eval(Environment localE) {

// as before evaluate receiver and arguments: o, argsE

...

// new code: determine sender, build and send message

Instance self = (Instance)(localE.lookup("this").read());

Msg msg = new Msg(self, o, this.methodId, argsE);

return self.send(msg);

}

}

Figure 20: Alternative original interpreter

class InstanceWithSenderTrace extends Instance {

Data send(Msg msg) {System.out.println("method called " + msg.methodId

+ " by " + msg.from);

return super.send(msg);

}

}

Figure 21: (User-defined) extension of Instance

22

Page 23: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 23/26

In the original interpreter, ExpMethod.eval() evaluates a method call by implementing the

composition lookupMethod();apply(). So, the behavior of the receiver of a method call can

be modified easily by changing the definition of lookupMethod() (as illustrated by trace insertionin the Section 5). However, a modification concerning the sender of the method call (see CODA

[aff95] for a motivation of making the sender explicit in the context of distributed programming) is

much more difficult to implement. Such a change would require the modification of all instances of 

ExpMethod in the abstract syntax tree, i.e. all occurrences of the operator ’.’. Indeed, we have to

check whether the object this in such contexts has a non-standard behavior.

A solution to this problem is to modify the non-reflective interpreter, such that its reflective version

provides a MOP enabling explicit access to the sender in a method call. Intuitively, we split message

sending in two parts: the sender side and the receiver side. First, we introduce a new class Msg which

is a four-tuple. For each method call, it contains the sender from, the receiver to, the method name

methodId and the corresponding argument values argsE. Then, two methods dealing with mes-

sages are added to the definition of Instance in the original interpreter: send() and receive()(see Figure 20). Finally, ExpMethod.eval() is redefined such that it creates and sends a message

to the receiver.

This new version of the non-reflective interpreter is made reflective by applying our program

transformation. Then, the user can, for example, introduce tracing for message senders (see Figure

21), the same way traces have been introduced in the previous section.

This example highlights three advantages of our approach: MOPs are precisely defined, applica-

tion programmers are provided with the minimal MOPs tailored to their needs and language designers

can extend MOPs at compile time without anticipation of these changes.

9 Related work

A comparison between reflective systems is inherently difficult because of the wide variety and the

conceptual complexity of reflective models and implementations. For example, the detailed defini-

tion of the CLOS MOP requires a book [kic91] and a thorough comparison between CLOS and

SMALLTALK already fills a book chapter [coi93].

Consequently, we restrict our comparison to the three basic properties our reflection model obeys

(the first and second characterizing Smith-like approaches, the third being fundamental to our goal of 

the construction of specially-tailored MOPs):

1. (tower) There is a potentially infinite tower of reflective interpreters.

2. (interpreter) The interpreter at level   interprets the code of the interpreter at level ¡ £ .

3. (selectivity & completeness) Any part of the runtime system and almost all of the syntax tree

(see Section 6) of an interpreter at level   can be reified and has an accessible representation at

level  

 

£ .

First, most reflective systems are based on some notion of reflective towers and provide a potentially

infinite number of levels. A notable exception to this are OPEN-C++ [chi95] and IGUANA [gc96]

whose MOPs only provide one metalevel.

Second, our approach is semantics-based following Smith’s seminal work on reflective 3-LISP

[smi84] for functional languages. This is also the case for the prototype-based languages 3-KRS

[mae87] and AGORA [meu98]. The other object-oriented approaches to reflection (including OBJ-

VL ISP [coi87], SMALLTALK [bri89] [riv96], CLASSTALK [bri89], CLOS [kic91], MetaXa [gol97])

23

Page 24: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 24/26

are not semantics-based (in the sense of the second property cited above) because they do not feed

higher-level interpreters with the code of lower-level interpreters. Instead, different levels are repre-

sented by appropriate pointer structures. This proceeding allows more efficient implementations buthas no semantic foundation. Moreover, these reflective languages are monolithic entities while our

modular approach consists of three simple parts: a non-reflective interpreter, the operator ¥ and the

operators   

.

Third, our approach enables language designers to precisely select which mechanisms of the lan-

guage are reflective. With the exception of IGUANA and OPEN-C++, all the reflective systems cited

above do not have this characteristic. Finally, note that our approach shares a general notion of com-

pleteness with 3-LISP, 3-KRS and AGORA: the programming model is defined by the interpreter

and almost all of its features can be made reifiable (“up” and “down” are primitives in 3-L ISP and

cannot be reified, for instance). Asai et al. [amy96] also starts from such a complete model but this

interesting approach to reflection in functional languages restricts reifiable entities in order to allow

optimization by partial evaluation. In contrast, the remaining reflective systems described above donot base reflection on features of an underlying interpreter but implement an ad hoc MOP. The notion

of completeness therefore does not make sense for them.

10 Conclusion and future work

In this paper we have presented a program transformation technique to generate reflective object

oriented interpreters from non-reflective ones. This technique allows specially-tailored MOPs to be

produced quickly. New MOPs can be developed from scratch or by refinement from existing ones

as exemplified in Section 8. Compared to general MOPs, specially-tailored ones could be tuned, for

instance, towards better efficiency and security properties.

To the best of our knowledge, the resulting framework for reflective object-oriented languages isthe first one satisfying the three basic properties mentioned in Section 9. Consequently, our approach

cleanly distinguishes between reifiable and non-reifiable entities, thus helping the understanding of 

reflective programs.

A prototype implementation, called METAJ [metaj], is available.

Future work. We presented a generic reification technique for object-oriented reflective languages,

which provides a basis for the exploration of the metaprogramming design space, optimization tech-

niques and the formalization of reflective systems.

First, at the system level the design space of MOPs should be explored by defining and refining

different non-reflective interpreters as exemplified in Section 8, yielding a taxonomy of reflective

mechanisms. At the user level, the proliferation of reflective dialects requires appropriate design and

programming tools, including libraries of user-friendly reflective operators, program analyses and

type systems.

Second, reflection is deeply related to interpretation. Each dispatch object introduces a new inter-

pretation layer by calling the method eval(). So, specialization techniques like partial evaluation

[bn00] are prime candidates for efficiency improvements. Furthermore, user-written reflective pro-

grams may not use all reflective capabilities provided by a reflective interpreter (e.g. only make use

of a bound number of reflective levels). In this case, optimization techniques such as that presented

by Asai et al. [amy96] could be used to merge interpretation levels.

Third, since reflective programming is a rather complex task, it should be based on a formal

semantics, e.g. to define and ensure security properties. We believe that our transformation could be

24

Page 25: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 25/26

used to generate specially-tailored reflective semantics from a non-reflective one.

Finally, we firmly believe that our reification technique can also be applied to (parts of) applica-

tions instead of an interpreter in order to make them reflective (preliminary results can be found in arelated paper by the authors [ds00]).

Acknowledgements. We thank the anonymous referees for their numerous constructive comments

and the editor Olivier Danvy. The work reported here has also benefited from remarks by Kris de

Volder, Shigeru Chiba and Jan Vitek. It has been improved through many discussions with our col-

leagues Noury Bouraqadi, Mathias Braux and Thomas Ledoux.

References

[aff95] J. McAffer. Meta-Level Programming with CODA. Proceedings of ECOOP, LNCS 952,

Springer Verlag, pp. 190-214, 1995.

[amy96] K. Asai, S. Matsuoka, A. Yonezawa. Duplication and Partial Evaluation — For a Better

Understanding of Reflective Languages. Lisp and Symbolic Computation, 9(2/3), pp. 203-

241, 1996.

[bc00] G. Blair, R. Campbell (chairs). Workshop on Reflective Middleware, 2000.

http://www.comp.lancs.ac.uk/computing/RM2000/

[bn00] M. Braux, J. Noyé. Towards Partial Evaluating Reflection in JAVA. Proceedings of Workshop

on Partial Evaluation and Semantics-Based Program Manipulation, ACM Press, pp. 2-11,

2000.

[bri89] J.P. Briot, P. Cointe. Programming with Explicit Metaclasses in SMALLTALK. Proceedings

of OOPSLA, ACM SIGPLAN Notices, 24(10), pp. 419-431, 1989.

[chi95] S. Chiba. A Metaobject Protocol for C++. Proceedings of OOPSLA, ACM SIGPLAN No-

tices, 30(10), pp. 285-299, 1995.

[coi87] P. Cointe. Metaclasses are First Class Objects: the OBJVL ISP Model. Proceedings of OOP-

SLA, ACM SIGPLAN Notices, 22(12), pp. 156-162, 1987.

[coi93] P. Cointe. CLOS and SMALLTALK: a comparison. In “Object-Oriented Programming: The

CLOS perspectives?”, A. Päpcke (ed.), MIT Press, ch. 9, pp. 215-274, 1993.

[coi99] P. Cointe (ed.). Proceedings of Reflection’99, LNCS 1616, Springer Verlag, 1999.

[dm88] O. Danvy, K. Malmkjær. Intensions and Extensions in a Reflective Tower. Proceedings of 

the ACM Conference on Lisp and Functional Programming, pp. 327–341, 1988.

[ds00] R. Douence, M. Südholt. On the lightweight and selective introduction of reflective capabil-

ities in applications. International Workshop on Reflection and Metalevel Architectures at

ECOOP, 2000.

ftp://ftp.disi.unige.it/person/CazzolaW/EWRMA/sudholt.ps.gz

[ghjv95] E. Gamma, R. Helms, R. Johnson, J. Vlissides. Design Patterns. Addison-Wesley, 1995.

25

Page 26: A Generic Reification Technique

7/27/2019 A Generic Reification Technique

http://slidepdf.com/reader/full/a-generic-reification-technique 26/26

[gol83] A. Goldberg, D. Robson. SMALLTALK 80, the Language and its Implementation. Addison-

Wesley, 1983.

[gol97] M. Golm. Design and Implementation of a Meta Architecture for Java. Master’s Thesis.

Universität Erlangen, 1997.

[gc96] B. Gowing, V. Cahill. Meta-Object Protocols for C+ +: The IGUANA Approach. Informal

Proceedings of Reflection’96, pp. 137-152, 1996.

[iyl95] J.-I. Itoh, Y. Yokote, R. Lea. Using Meta-Objects to Support Optimisation in the Apertos Op-

erating System. Proceedings of the USENIX Conference on Object-Oriented Technologies

(COOTS), USENIX Association, pp. 147-158, 1995.

[java] JAVA home page. Sun Microsystems, Inc. http://java.sun.com

[kic91] G. Kiczales, J. des Rivières, D. Bobrow. The Art of the Metaobject Protocol. MIT Press,1991.

[mae87] P. Maes. Concepts and Experiments in Computational Reflection. Proceedings of OOPSLA,

ACM SIGPLAN Notices, 22(12), pp 147-155, 1987.

[mdc96] J. Malenfant, C. Dony, P. Cointe. A Semantics of Introspection in a Reflective Prototype-

Based Language. Lisp and Symbolic Computation, 9(2/3), pp. 153-180, 1996.

[mf93] A. Mendhekar, D. P. Friedman. Towards a Theory of Reflective Programming Languages.

Informal Proceedings of the Third Workshop on Reflection and Metalevel Architectures in

Object-Oriented Programming at OOPSLA, 1993.

[metaj] METAJ home page: http://www.emn.fr/sudholt/research/metaj.

[meu98] W. De Meuter. AGORA: The Story of the Simplest MOP in the World. In “Prototype-based

Programming”, J. Noble et al. (ed.), Springer Verlag, 1998.

[mul92] R. Muller. M-LISP: A Representation-Independant Dialect of LISP with Reduction Seman-

tics. ACM TOPLAS, 14(4), pp. 589-616, 1992.

[riv96] F. Rivard. SMALLTALK: a Reflective Language. Informal Proceedings of Reflection’96, pp.

21-38, 1996.

[smi84] B.C. Smith. Reflection and Semantics in LISP. Proceedings of POPL, ACM Press, pp. 23-35,

1984.

[wf88] M. Wand, D. P. Friedman. The Mystery of the Tower Revealed: A Non-Reflective Descrip-

tion of the Reflective Tower. Lisp and Symbolic Computation, 1(1), pp. 11-38, 1988.

26