Research: Linguistic Symbiosis

Introduction

Linguistic symbiosis is a concept that arose from work on reflectively extensible interpreters. In the work of Ichisugi et al. and Steyaert and De Meuter, linguistic symbiosis refers to the ability of base-level objects and meta objects to send each other messages when the base-level and meta-level languages are not the same. The concept was so far however only explored in cases where the base-level and meta-level languages were both object-oriented languages. Together with a few colleagues I am researching the extension of the concept to language combination in multi-paradigm programming.

We have so far experimented with the concept of linguistic symbiosis mostly in combining logic and object-oriented programming. I myself have in particular focused on Smalltalk and SOUL. SOUL is a logic language developed at the PROG lab and implemented in Smalltalk itself. While SOUL already supported a form of interaction with Smalltalk in its earliest incarnations, we have evolved this further into a more transparent form of interaction, as is explained further below.

Linguistic Symbiosis and Multi-Paradigm Programming

Writing software using multiple paradigms obviously necessitates a way of combining programs written in languages of differing paradigms. Combining programs is done by letting one or both invoke the functionality of the other, requiring a mechanism for inter-language interaction or communication to be present in the languages.

There are several ways in which such inter-language communication can be achieved. A classic way is to use a pipes-and-filters architecture or some sort of shared buffer for communication. Programs can then communicate with each other by passing data through the buffer or pipe, preceded by a request tag in response to which the other program manipulates the data and passes results back through the buffer. This approach typically is a "lowest common denominator" one, where only primitive data values - numbers and characters - can be passed, requiring each program to re-assemble more complex structures. Another disadvantage is that, unless the languages used are stream programming languages, the use of 'tags' to represent what functionality to invoke requires the program to translate these tags to the actual unit of functionality in that language, be it procedures, methods, rules or something else.

Linguistic symbiosis aims for a much tighter and smoother inter-language communication. An ideal case is exemplified by Agora, an object-oriented language which has been combined with Smalltalk, Java and even C++. Agora programs and programs in Smalltalk/Java/C++ can pass objects to each other, not just primitive values, and objects written in either language can be manipulated through message sending in the other as if they were written in that same language. Implementation-wise, this is achieved through the use of proxies. This is illustrated in the figure below: objects (depicted with circles and rectangles) can be passed from one OO language to the other, where they are then represented by a proxy (depicted with circles/rectangles enclosed in a rectangle/circle). The proxy in one language can be sent messages like any other object in that language, but it translates these to messages in the other language. A suitable translation function for messages needs to be defined, especially if the syntax and exact semantics of message sends is different in the two languages. This is for example the case with Agora and Java: Agora employs a Smalltalk-like keyword-based message syntax, where the actual message consists of a keyword for each argument, while a message in Java consists of a single name. Furthermore, method lookup in Java depends on the static type of arguments, while Agora is dynamically typed. A convention was therefore established whereby Java methods are mapped to Agora messages by taking the name of the static types of the arguments as keywords, with the first keyword prefixed with the name of the method as well. A similar solution is used for the inverse interaction. No special language construct is thus needed to let Agora and Java interact. This has the added benefit that parts of Java/Agora can be overridden in the other language.


Illustration of the mechanisms underlying linguistic symbiosis in the context of combining two object-oriented languages. (click to zoom)

In a multiparadigm setting, linguistic symbiosis becomes more complex. In combining two OO-languages, one only needs to figure out how objects can be passed between the two languages and define a suitable mapping function between messages in both languages. In a multiparadigm setting this becomes a problem of, for example in combining object-oriented and logic programming, mapping messages and objects to rule conditions and terms. We've researched this further for SOUL and Smalltalk, as is explained in the next section.

SOUL and Smalltalk: towards Linguistic Symbiosis

SOUL is a Prolog-like logic language developed on top of Smalltalk. In earlier versions, SOUL supported the basic mechanism underlying linguistic symbiosis: allowing the values of one language to be used in the other. However, SOUL featured a special linguistic construct for manipulating objects from the logic language: Smalltalk terms. Smalltalk terms are logic terms consisting of Smalltalk expressions enclosed in square brackets. When Smalltalk terms are used as conditions in rules, they are "proven" by the logic inferencer simply by executing the Smalltalk expression. The expression is expected to return true or false, indicating the success or failure of the "proof". An example is given in the following code.

personNamed(?person, ?name, ?database) if
  database(?database),
  member(?person, [ ?database allElements ]),
  [ ?person isPerson ],
  equals(?name, [ ?person name ])

The above code shows how Smalltalk terms are used in the definition of a personNamed predicate. In this small example persons are represented as objects contained in a database object in Smalltalk, while logic queries are used to query the database. The personNamed rule above specifies when ?person is such a Person object named ?name and contained in a specific database ?database. The above rule uses Smalltalk terms in two ways: in the member and equals condition a Smalltalk term is used to get a value from a Smalltalk object into the logic program, and secondly a Smalltalk term is used to specify a boolean condition on a Smalltalk object. The member predicate was extended in SOUL to also work on Smalltalk collections, thus in the rule above it is used to specify that ?person should be in the collection of all elements of the database, which is retrieved by sending the ?database object the allElements message.

The problem with Smalltalk terms is that they are a special linguistic construct added to Prolog. There is thus a clear distinction between "logic conditions" and "Smalltalk conditions", which inhibits the added benefit of linguistic symbiosis: the possibility to re-implement logic rules as Smalltalk methods and vice-versa without having to change the code that used the rule or method.

To remove Smalltalk terms from SOUL we established a translation function for messages and predicates. Using this translation function, the explicit language switching of Smalltalk terms was replaced with an implicit mechanism in the rule lookup of SOUL: when no rule is found for a predicate, the predicate is translated to a message. Vice-versa, the explicit SOUL invocation mechanism in Smalltalk was replaced with an implicit mechanism in Smalltalk's method lookup: when no method is found for a message, it is translated to a logic predicate. In this way one no longer has to explicitely specify "escapes" from one language into the other, and one does not have to add or remove these "escapes" when a rule is turned into a method or vice-versa.

A first attempt at a translation function is [TODO]

While the first attempt at translation of messages to predicates and vice-versa showed the mechanism to be clearly possible, the resulting message and predicate names were not very readable. This was later solved by changing the syntax of SOUL to be more like Smalltalk's, hence also simplifying the message/predicate translation. The new and old syntaxes are contrasted in the figure below.

This history of the evolution of SOUL and Smalltalk towards linguistic symbiosis is further described in the paper "SOUL and Smalltalk - Just Married".

Cases

Business Rules

Conducted together with Maja D'Hondt

Software applications often contain implicit knowledge. Examples are policies, preferences, decisions, processes, workflows and so on. We focused on rule-based knowledge where each rule is an atomic unit of knowledge that states how to infer knowledge or initiate actions when certain conditions are satisfied.

When making rule-based knowledge explicit, the most suitable representation for rules is logical implications in a logic reasoning system. A crucial advantage of such systems is that the flow of rules is automatically managed by a rule engine. The software's core application functionality, on the other hand, is best expressed in an object-oriented programming language. Hence our interest in using a multi-paradigm programming approach for the implementation of business software.


Interaction between business objects and business rules.

Generative Component Composition Rules

In generative programming, software for a particular domain is generated from a description in a domain-specific language. The software generator consists of two parts: a set of components implementing specific features the software may exhibit and a program which derives the low-level component composition from the domain-specific description. The latter is usually done using C++ meta programming, but logic programming is obviously a good candidate for such a deduction process as well.

Generative programming is most often seen as dealing with purely static generation, but dynamic generation can be necessary as well. When we need to dynamically reconfigure software, we would also like to do this on the basis of the domain-specific description. In this case we may need an addition to the generator: a part which detects whether the reconfigured composition is to be allowed. Taking the classic "bank account" example of generative programming: adding an overdraft feature to an existing bank account should not be allowed when the owner of the bank account is younger than 21. It is for specifying such additional "dynamic state" constraints that it becomes interesting to have linguistic symbiosis between the language used for the components and the logic language used in the two other parts of the generator (for the low-level composition deduction and reconfiguration possibilities).

The use of linguistic symbiosis between logic and OO programming for reconfiguration rules in the bank account example was further explored in the paper "Enforcing Feature Set Correctness for Dynamic Reconfiguration with Symbiotic Logic Programming".

Aspect Weaver for Transaction Management Aspects

Conducted together with Johan Fabry

Johan Fabry is researching the specification of transaction management aspects. For this, he developed an aspect weaver implemented in Smalltalk. The weaver included some methods which compute exactly where transaction management code should be woven. We found these methods really contained rule-based knowledge and could be more easily written as logic rules. By using "linguistic symbiosis"-enabled SOUL and Smalltalk we were able to reimplement the methods as logic rules, without having to change the messages that normally invoked the methods.

Artefacts

By myself:

By others:

Related Publications

Note that this list is infrequently updated and may therefore not contain my most recent publications regarding linguistic symbiosis, you can always check out my publications page as well.

Some other relevant publications of which I am not a co-author:

Other Work on Combination of Programming Languages

Logic + OO Combination

OO + OO Combination

Others