Section 11.2. PICCOLA


11.2. PICCOLA

In this section, we give an overview of the layered architecture of Piccola itself, a brief example of generic wrappers in Piccola, and an overview of the kinds of composition abstractions that can be conveniently expressed in Piccola.

Piccola is designed to be a composition language, rather than a general purpose programming language. As such, it reduces software composition to a bare minimum of core mechanisms, that is, forms, agents, and channels, which can then be used to define higher-level abstractions.

Forms. A form is an extensible record. For example, a=(x=1, y=2) defines a form a that binds labels x and y. We can project a label in a form, such as w=a.x, or we can extend a form with new bindings; for example, b=(a, z=3) extends a with a binding for label z. A form is also a namespace; for example, ('a, x+y) evaluates x+y in the namespace a.

Services. A service is a function over forms. println is a standard Piccola service that prints its argument form. newPoint p:(x=p.x, y=p.y) takes a form p as its argument and returns a form that extracts just the x and y bindings from p. Note that a service is also a form, so we can bind labels to services, extend services with bindings, or extend forms with services. (A service can be thought of as a form with single "call" label, just as a function object in C++ is an object with an operator() member function.)

Agents. The standard Piccola service run invokes the do service of its argument as a new, concurrent agent. (The code run(do:println "hello") creates a new agent that asynchronously prints "hello".)

Channels. A channel is an unbounded buffer that can be used to synchronize agents. newChannel() returns a form with send and receive services. send is non-blocking, while receive blocks if there is no data on the channel. As a simple example, stop:newChannel().receive() is a service that causes an agent to stop dead (i.e., it tries to read from a channel that no other agent ever writes to).

The formal semantics of Piccola is compactly expressed with the help of the Piccola-calculus [2, 22], a process calculus that extends Milner's p-calculus [19] with forms and services.

With these mechanisms, Piccola can express three complementary kinds of composition:

Namespace composition. A form can be extended with another form, yielding a new form.

Functional composition. Services can be invoked with a form as an argument, yielding a form as a result.

Agent composition. Concurrent agents can be composed and coordinated by means of shared channels.

11.2.1. Piccola Layers

Piccola is intended to be used in a layered fashion (see Table 11-1) to ultimately support a paradigm of "scripting" applications together from a set of software components [6]. In the ideal case, components constitute a kind of "component algebra" in which operators connect components and again yield components. Scripts, then, compose components, yielding up bigger components.

Table 11-1. Piccola Layers

Applications

Components + Scripts

Compositional styles

Streams, events, GUI composition, ...

Standard libraries

Basic coordination abstractions, built-in types

Piccola

Operator syntax, introspection, component wrappers

Piccola-calculus

Forms, agents, channels, services


At the lowest level, the Piccola run-time system provides nothing but the core mechanisms of the Piccola-calculus. Programming at this level would be like programming in a concurrent assembly language.

The next level defines the Piccola language. In Piccola, everything is a form, so Piccola hides the operators of the Piccola-calculus and models everything in terms of forms and services. There is no special syntax to express agents and channels, just standard services run and newChannel. Mechanisms for defining infix and prefix operators are also provided, which is convenient for specifying component connectors as compositional operators. More importantly, reflective mechanisms are provided for exploring forms, wrapping them, and wrapping existing components from a host programming language (i.e., Java or Squeak [11], in the current Piccola implementations).

When Piccola starts up, a number of standard libraries are loaded. At this level, Piccola provides access to a number of built-in types (i.e., Booleans, numbers, strings, collections, file streams, and so on). In most cases, these standard types wrap existing Java components to provide them with more convenient compositional interfaces. The standard libraries also provide a number of standard services that implement various common control structures in terms of forms, agents, and channels. Exception handling, for example, is implemented using two agents to run the try and catch blocks and a channel to coordinate them in case an exception is raised [6].

On top of the standard libraries, one may define various compositional styles that abstract away from the low-level wiring of the Piccola-calculus and provide instead higher-level plugs, or connectors corresponding to a problem domain. A simple GUI style, for example, that wraps Java AWT and Swing components can easily be defined in Piccola. Furthermore, the style gives us a simple component algebra in which a composition of GUI components is again a GUI component.

Finally, at the top level, one may use these styles to script together components. For example, the GUI style is used to build an interactive console for developing and testing JPiccola scripts (see Figure 11-1).

Figure 11-1. The JPiccola consolescripted from wrapped Java GUI components.


11.2.2. Generic Wrappers

Let us first consider the problem of defining a generic wrapper. wrapPrePost wraps each service of its argument form by invoking pre- and post-services before and after the original body. The implementation uses the built-in service forEachLabel to iterate over the labels of the argument.

 wrapPrePost Arg:   'wrappedForm = newVar()    # local variable   forEachLabel     form = Arg.form     do Label:                # wrap each service in Arg.form       wrappedForm.set               # update the result        'wrappedService Args:          Arg.pre()                  # invoke the pre-service          Label.project(form)(Args)  # invoke original service          Arg.post()                 # invoke the post-service        wrappedForm.get()            # get the result so far        Label.bind(wrappedService)   # add the new binding   wrappedForm.get()                 # return the wrapped form 

Although detailed explanation of the code is beyond the scope of this discussion (please see the JPiccola Guide for details [23]), a few observations may help the reader to follow this example. wrapPrePost is a service being defined that takes a single Arg form as its argument. Arg is expected to provide bindings for pre, post, and form. wrappedForm is a local binding (made local by the Piccola ' operator). newVar is a standard service that returns a persistent variable with get and set services. forEachLabel is another standard service, whose argument is the indented form on the following lines. This argument provides bindings for form (a value) and do (a service). forEachLabel generates a first-class representation of each label bound in the form. A first-class label is a form that represents a label and provides services such as bind, project, and restrict. The first-class labels are use to reflect over the structure of the argument form and build a new, wrapped representation.

A key point to notice is that all services in Piccola are monadic; that is, they always take a single form as an argument, rather than a tuple of forms. This makes the task of wrapping services much simpler than it would be in most programming languages.

We could now use this generic wrapper to wrap a component with an arbitrary synchronization policy. Suppose, for example, we define semaphores like this:

 newSemaphore:   'sem = newChannel()   p: sem.receive()   v: sem.send()   'v() 

and a mutual exclusion synchronization policy like this:

 newMutexPolicy:   'sync = newSemaphore()   pre:  sync.p()   post: sync.v() 

Now if F is some form, we can wrap each of its services with a mutual exclusion policy as follows:

 MutexF = wrapPrePost   form = F   newMutexPolicy() 

A readers/writers synchronization policy could be similarly defined. In this case, however, we must distinguish between reader and writer services and use the wrapPrePost service to wrap them separately with their own policies:

 bindRWPolicy Arg:   wrapPrePost     form = Arg.reader     pre  = Arg.policy.preR     post = Arg.policy.postR   wrapPrePost     form = Arg.writer     pre  = Arg.policy.preW     post = Arg.policy.postW 

We must now explicitly list the services to be wrapped:

 RWsynchedF = bindRWPolicy   policy = newRWPolicy()           # create rw policy   reader = (r1 = F.r1, r2 = F.r2)  # the reader methods   writer = (w = F.w)               # the writer method 

Generic wrappers play an important part in compositional styles, since they constitute a form of reusable "glue code" that can adapt components to different styles. Automatically invoked wrappers are used in Piccola, for example, to adapt Java components to compositional styles. Java AWT and Swing components, for example, are automatically wrapped when they are accessed by a Piccola script, allowing them to be connected using the GUI style defined in Piccola.



Aspect-Oriented Software Development
Aspect-Oriented Software Development with Use Cases
ISBN: 0321268881
EAN: 2147483647
Year: 2003
Pages: 307

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net