Roles in Eclipse Indigo

43 views
Skip to first unread message

Stephan Herrmann

unread,
Aug 19, 2011, 8:27:27 AM8/19/11
to object-composition
Hi,

I think some readers in this group might be interested to hear that
programming with contextual roles is now supported by Eclipse 3.7
basically out of the box:

The Eclipse Object Teams Project[1] has joined the Eclipse
simultaneous release train which means the Object Teams Development
Tooling is part of this year's Eclipse release, called "Indigo" [2].
When the project moved to Eclipse in January 2010 the rules of the
Eclipse Foundation required us to carry the "Incubation" label and use
version numbers below 1.0. With the Indigo release the project has
graduated and published its release 2.0.0 (successor of 1.4.x releases
prior to the move to Eclipse).
I had heard that the "Incubation" label stopped some people from
adopting Object Teams. This reason has gone, the mature 2.0.0 release
reflects the result of almost 10 years of development.

A second reason of reluctance had been some uneasy feelings regarding
role instances being attached to base instances, leading some people
to speak of "object schizophrenia". An account of this issue has been
published in the ACM Digital Library: "Demystifying Object
Schizophrenia" [3], which concludes that split objects introduce
highly desirable flexibility and need not pose any problem *if* the
concepts are well integrated in the programming language, which is the
case in OT/J. I argue, that with these results the second reason of
reluctance has become insubstantial.

I admit, that for a potential third hindrance I have no news: apart
from small examples from the very early days of this list I have no
new example implementations demonstrating the useful application of OT/
J for the benefit of DCI. I was thinking of transcribing a common DCI
example to OT/J, but frankly I couldn't find what would be the most
suitable example. I'd want to do more than MoneyTransfer, so if there
is a more comprehensive, well-specified example for DCI please let me
know, or maybe someone else likes to give it a first shot of
implementing the current favorite example in OT/J?

cheers,
Stephan

[1] http://www.eclipse.org/objectteams/
[2] http://www.eclipse.org/indigo/projects.php
[3] http://dl.acm.org/citation.cfm?doid=1929999.1930001
pre-print: http://www.objectteams.org/publications/MASPEGHI10.pdf

Ant Kutschera

unread,
Aug 19, 2011, 10:18:10 AM8/19/11
to object-composition
Hi Stephan,

I think I would like to try OT/J.

What I don't quite understand with OT/J is how easy it is to write a
DCI solution.

I'm starting from the point of view that I have a few use cases I want
to implement, and I will create the same (or near as same as possible)
mental model for all implementations.

I want to then write some code, and compare each solution. Its
important that the mental models be as similar as possible.

So, a few questions about OT/J:

- Can I simply use the roles without teams?
- What is a team? Does it relate to anything in DCI?

Gruss aus Bern,
Ant

Stephan Herrmann

unread,
Aug 19, 2011, 10:35:58 AM8/19/11
to object-composition
Hi Ant,

On Aug 19, 4:18 pm, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> I think I would like to try OT/J.

great.

> What I don't quite understand with OT/J is how easy it is to write a
> DCI solution.

Let's find out. If you have questions specific to OT/J you're also
welcome to ask at http://www.eclipse.org/forums/eclipse.objectteams

> [...]
>
> So, a few questions about OT/J:
>
> - Can I simply use the roles without teams?
> - What is a team? Does it relate to anything in DCI?

The team is the context roles live in. OT/J does not allow you to
write roles without a team, which shouldn't be a limitation for DCI,
should it?

> Gruss aus Bern,

Gruss aus Berlin,
Stephan

Ant Kutschera

unread,
Aug 19, 2011, 11:46:00 AM8/19/11
to object-composition
Ok, if a team is a context, I can see where to start. Ill be in
touch, when I get that far.

Ant

Ant Kutschera

unread,
Aug 20, 2011, 3:44:13 AM8/20/11
to object-composition
Hi Stephan,

Cool!

I haven't read all the documentation (there's lots!)

But, I wrote the following. The use case is related to what I posted
on the other thread. A customer gives some books to a cashier to buy
them, the cashier uses the till to do the sale, and the till creates
some accounting records while playing the role of Accountant:

----------------- the context -----------------
package ch.maxant.till.otj;

import java.util.List;
import ch.maxant.till.common.data.Book;

/** this is the sales context in which a till creates accounting
records */
public team class SalesContext {

private final Till till;

public SalesContext(Till till) {
this.till = till;
}

/** this is the role which a till can play in the sales context. in
this
* context, the till creates some accounting records */
public class Accountant playedBy Till {

public void createAccount(List<Book> books){
for(Book b : books){
System.out.println("Creating an accounting record for: " + books);
}
}

}

/** interaction method on the context. calling this, starts the
interaction */
public void doSale(List<Book> booksToSell){

//cast till into accountant role
Accountant a = new Accountant(till);

//start interaction
a.createAccount(booksToSell);
}

}



----------------- the code which calls the context: -----------------

Book b = new Book("JQuery in Action, Second Edition",
"234-34565-654-44", new BigDecimal("47.99"));

Till till = new Till();

SalesContext c = new SalesContext(till);
List<Book> booksToSell = new ArrayList<Book>();
booksToSell.add(b);
c.doSale(booksToSell );

System.out.println("Done");
----------------------------------
The Book and Till classes have no behaviour themselves.
----------------------------------

It works, the output is:

Creating an accounting for: [Book [title=JQuery in Action, Second
Edition, productCode=234-34565-654-44, price=47.99]]
Done

My questions:

1) Is this how you would do it?
2) is there any different way to assign the role to the till object,
like say with the "->" operator, rather than using the role
constructor?
3) I could do almost exactly the same, using standard Java, if I
ommitted "playedBy Till" and simply created my own constructor in
"Accountant" which takes a Till. What have I missed?

I appreciate OT/J is bigger than DCI and has a lot more to offer :-)

Thanks,
Ant

Stephan Herrmann

unread,
Aug 20, 2011, 10:31:48 AM8/20/11
to object-composition
Hi Ant,

that's a nice start.


> 1) Is this how you would do it?

We could let OT/J help us in achieving the following separation of
concerns:

Roles are purely internal to the context, so we may declare
Accountant, e.g., as protected (which in OT/J is actually a pretty
strong level of protection).

Conversely, we may want to implement the context independent of the
players like Till.

I start establishing this separation by placing Till into the package
ch.maxant.till.common.data (do you have a better place for it?). And
then a special import is added:

import base ch.maxant.till.common.data.Till;

See the "base" modifier? This restricts the use of Till so that it is
OK to say "playedBy Till", but it's no longer OK to create a field of
type Till. The idea is that inside the context we always refer to a
Till as the Accountant role it plays in this context, so I'd change
the field from Till to Accountant:

private Accountant accountant;


> 2) is there any different way to assign the role to the till object, like say with the "->" operator, rather than using the role constructor?

Yep, and as we cannot mention Till anymore, we actually *have* to move
the role assignment, it no longer is an implementation detail but s.t.
that happens at the boundary between the context and the "outside",
e.g.: if a SalesContext must always have an Accountant I'd say (inside
SalesContext):

public SalesContext(Till as Accountant till) {
accountant = till;
}

The invocation would still say:

SalesContext c = new SalesContext(till);

The "as" keyword translates the Till player to its Accountant role (OT/
J-speak: "lifting").

Of course now the implementation of doSale is reduced to

public void doSale(List<Book> booksToSell) {
//start interaction
accountant.createAccount(booksToSell);
}

Maybe this is all you'd want to do in this example:
----------------------------------
package ch.maxant.till.otj;

import java.math.BigDecimal;
import java.util.List;

import ch.maxant.till.common.data.Book;
import base ch.maxant.till.common.data.Till;

/** this is the sales context in which a till creates accounting
records */
public team class SalesContext {

private final Accountant accountant;

public SalesContext(Till as Accountant till) {
accountant = till;
}

/**
* this is the role which a till can play in the sales context.
* in this context, the till creates some accounting records.
*/
protected class Accountant playedBy Till {
public void createAccount(List<Book> books) {
BigDecimal sum = BigDecimal.ZERO;
for (Book b : books) {
System.out.println("Creating an accounting record for:
" + b);
sum = sum.add(b.getPrice());
}
System.out.println("Sum is "+sum);
}
}

/**
* interaction method on the context.
* calling this, starts the interaction
*/
public void doSale(List<Book> booksToSell) {
// start interaction
accountant.createAccount(booksToSell);
}
}
----------------------------------


> 3) I could do almost exactly the same, using standard Java, if I ommitted "playedBy Till" and simply created my own constructor in "Accountant" which takes a Till. What have I missed?

I don't know? :) If you're fine with the implementation (maybe after
the adjustments outlined above), go ahead, maybe just these three
concepts suffice for typical DCI implementations:

- playedBy: declare role class for a given player class
- import base: refer to a player class for the sole purpose of role
assignment
- as: translate a given player to its role.

Do you see any pain points with the above implementation? Anything
we'd want to improve? Maybe we need more details, e.g., what are the
properties of a Till / an Accountant?

> I appreciate OT/J is bigger than DCI and has a lot more to offer :-)

The basic message here is: when your applications get a bit more
involved/large/complex you may rest assured that also advanced issues
are addressed by the language. Learn'em as you need'em.

best,
Stephan

Stephan Herrmann

unread,
Aug 20, 2011, 11:21:52 AM8/20/11
to object-composition
Hi again,

Anticipating a possible next step, I've played with adding also a role
for Books: ItemForSale.

This would basically follow the scheme of my previous post, only the
signature "doSale(List<Book> booksToSell)" no longer works out of the
box. I suggest to add another field in the context:

private final List<ItemForSale> itemsToSell = new
ArrayList<ItemForSale>();

plus a team method:

public void addBookToSell(Book as ItemForSale book) {
itemsToSell.add(book);
}

Now the invocation would change to:

SalesContext c = new SalesContext(till);
c.addBookToSell(b); // repeat for all your books
c.doSale();

(If you think this is a severe restriction feel free to chime in at
https://bugs.eclipse.org/355296).

Another question arises once we want to access properties of the Book-
as-ItemToSell: In OT/J the player's properties are not automatically
visible when looking at the role (another concept for decoupling both
worlds), however, exposing those properties is very easy:

protected class ItemForSale playedBy Book {
BigDecimal getPrice() -> BigDecimal getPrice();
toString => toString;
}

Now getPrice() and toString() are available as methods of ItemForSale,
refering to the implementation from the player class Book, i.e., we
have created our context specific view of Book.
(The second declaration omits the signatures, which is legal, because
the signature of toString is already inherited from Object; secondly,
the => token gives a hint that the inherited method is overridden by
this declaration).
In OT/J-speak these are "callout bindings", there's a wealth of things
you can adjust in a callout binding (details are in
http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.objectteams.otdt.doc%2Fguide%2Fdevelop.html).

Here's the result of applying the above changes, where we now see how
the interaction method indeed triggers the interaction among two or
more roles:


----------------------------------
package ch.maxant.till.otj;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import base ch.maxant.till.common.data.Book;
import base ch.maxant.till.common.data.Till;

/** this is the sales context in which a till creates accounting
records */
public team class SalesContext {

private final Accountant accountant;
private final List<ItemForSale> itemsToSell = new
ArrayList<ItemForSale>();

public SalesContext(Till as Accountant till) {
accountant = till;
}

/**
* If a book is playing the role of an item for sale,
* it is fully described by it's string representation
* and its price.
*/
protected class ItemForSale playedBy Book {
BigDecimal getPrice() -> BigDecimal getPrice();
toString => toString;
}

/**
* this is the role which a till can play in the sales context.
* in this context, the till creates some accounting records.
*/
protected class Accountant playedBy Till {
public void createAccount(List<ItemForSale> books) {
BigDecimal sum = BigDecimal.ZERO;
for (ItemForSale b : books) {
System.out.println("Creating an accounting record for:
" + b);
sum = sum.add(b.getPrice());
}
System.out.println("Sum is "+sum);
}
}

public void addBookToSell(Book as ItemForSale book) {
itemsToSell.add(book);
}

/**
* interaction method on the context.
* calling this, starts the interaction
*/
public void doSale() {
accountant.createAccount(itemsToSell);
}
}
----------------------------------
Book b = new Book("JQuery in Action, Second Edition",
"234-34565-654-44",
new BigDecimal("47.99"));

Till till = new Till();

SalesContext c = new SalesContext(till);
c.addBookToSell(b);
c.doSale();

System.out.println("Done");
----------------------------------

So, maybe the list of OT/J concepts for basic DCI implementations
should mention four concepts:
- playedBy: declare role class for a given player class
- import base: refer to a player class for the sole purpose of role
assignment
- as: translate a given player to its role.
- callout: declare which properties of the player should be exposed at
the role level.

best,
Stephan

Stephan Herrmann

unread,
Aug 20, 2011, 11:24:13 AM8/20/11
to object-composition
> In OT/J-speak these are "callout bindings", there's a wealth of things
> you can adjust in a callout binding (details are inhttp://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.objectt...).

ups, wrong link, should've been:
http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.objectteams.otdt.doc%2Fguide%2Fotjld%2Fdef%2Fs3.html

Stephan

Ant Kutschera

unread,
Aug 20, 2011, 1:25:06 PM8/20/11
to object-composition
Wow, thanks for the help.

Those concepts are perfect, so I will take a look, thanks.

All I need now, is to identify the roles, but that's the other
thread...

Ant

Ant Kutschera

unread,
Aug 20, 2011, 5:28:36 PM8/20/11
to object-composition
OK, I've implemented the following, and it all feels very good.

The only thing I can think of at the moment, is that the Context
depends explicitly on specific data types (Book and Till). I've
discussed this on object-composition before - I don't like it, because
in my opinion, the role & context author should not be dependent upon
specific data types, but rather only on the "role contract", which is
the set of methods which a data class must contain, for an object of
that class to play the role.

I tried replacing "Till" with "Object", but that can't compile,
because it cant "lift" from accountant to object, only from accountant
to till. Kind of makes sense, if you want to catch problems at
compile time rather than runtime.

I guess you have no solution for this?

Ant


------ context & roles ------

package ch.maxant.till.otj;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

//"base", so that it is only possible to use till with the "playedBy"
keyword
import base ch.maxant.till.common.data.Book;
import base ch.maxant.till.common.data.Till;

/**
* this is the sales context in which a till creates accounting
records
*/
public team class SalesContext {

private final Accountant accountant;
private List<Product> productsToSell;

public SalesContext(Till as Accountant till) {
this.accountant = till;
productsToSell = new ArrayList<Product>();
}

/** setup method on context, to be called before starting the
interaction. */
public void addProduct(Book as Product book){
this.productsToSell.add(book);
}

/** interaction method on the context. calling this, starts the
interaction */
public void doSale(){

System.out.println("At start of interaction, the till has this much
cash: " + accountant.getCashBalance());

//start interaction
accountant.createAccountEntries(productsToSell);

System.out.println("OT/J Complete. Till now has this much cash: " +
accountant.getCashBalance());
}

/** this is the role which a till can play in the sales context. in
this
* context, the till creates some accounting records */
protected class Accountant playedBy Till {

//Accountant can call setCashBalance on the Till
private void setCashBalance(BigDecimal newBalance) => void
setCashBalance(BigDecimal newBalance);
protected BigDecimal getCashBalance() => BigDecimal
getCashBalance();

protected void createAccountEntries(List<Product> products){
BigDecimal total = BigDecimal.ZERO;
for(Product p : products){
total = total.add(p.getPrice());
System.out.println("Creating an accounting record for: " + p);
}

setCashBalance(getCashBalance().add(total));
}
}

/** a simple role, describing the products we use in the sales
context */
protected class Product playedBy Book {
protected BigDecimal getPrice() => BigDecimal getPrice();
}

}


----- test code to run the sample -----

package ch.maxant.till.otj;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import ch.maxant.till.common.data.Book;
import ch.maxant.till.common.data.Till;

public class TillRunner {

public static void main(String[] args) {

List<Book> booksToSell = new ArrayList<Book>();
Book b = new Book("JQuery in Action, Second Edition",
"978-1-935182-32-10", new BigDecimal("44.99"));
booksToSell.add(b);
b = new Book("BPM Basics for Dummies", "978-0-470-28571-8", new
BigDecimal("26.95"));
booksToSell.add(b);

Till till = new Till(new BigDecimal("200.00"));

SalesContext c = new SalesContext(till);


for(Book book : booksToSell){
c.addProduct(book);
}
c.doSale();

System.out.println("Done");
}
}


----- data classes -----

package ch.maxant.till.common.data;

import java.math.BigDecimal;

public class Book {

private String title;
private String productCode;
private BigDecimal price;
public Book(String title, String productCode, BigDecimal price) {
super();
this.title = title;
this.productCode = productCode;
this.price = price;
}
public String getTitle() {
return title;
}
public String getProductCode() {
return productCode;
}
public BigDecimal getPrice() {
return price;
}
@Override
public String toString() {
return "Book [title=" + title + ", productCode=" + productCode
+ ", price=" + price + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((price == null) ? 0 : price.hashCode());
result = prime * result
+ ((productCode == null) ? 0 : productCode.hashCode());
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Book other = (Book) obj;
if (price == null) {
if (other.price != null)
return false;
} else if (!price.equals(other.price))
return false;
if (productCode == null) {
if (other.productCode != null)
return false;
} else if (!productCode.equals(other.productCode))
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}

}


package ch.maxant.till.common.data;

import java.math.BigDecimal;

public class Till {

private BigDecimal cashBalance;
private double paperAvailability;

public Till(BigDecimal cashBalance) {
paperAvailability = 95.0; //TODO get it from the printer API
this.cashBalance = cashBalance;
}

public BigDecimal getCashBalance() {
return cashBalance;
}

public void setCashBalance(BigDecimal cashBalance) {
this.cashBalance = cashBalance;
}

public double getPaperAvailability() {
return paperAvailability;
}

public void setPaperAvailability(double paperAvailability) {
this.paperAvailability = paperAvailability;
}


}


Ant Kutschera

unread,
Aug 20, 2011, 5:30:54 PM8/20/11
to object-composition
PS - here is the almost identical solution, using my framework
(www.maxant.co.uk/tools.jsp) - it does let you have a context & roles
which are independent of the data classes.

----- context and roles -----

package ch.maxant.till.dci;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import ch.maxant.dci.util.BehaviourInjector;
import ch.maxant.dci.util.Context;
import ch.maxant.dci.util.Role;
import ch.maxant.dci.util.Self;

/** this is the sales context in which an object is given the ability
to create accounting records */
@Context
public class SalesContext extends BehaviourInjector {

private final Accountant accountant;
private List<Product> productsToSell;

public SalesContext(Object accountant) {
this.accountant = this.assignRole(accountant, Accountant.class);
productsToSell = new ArrayList<Product>();
}

/** setup method on context, to be called before starting the
interaction. */
public void addProduct(Object product){
Product assignSimpleRole = this.assignSimpleRole(product,
Product.class);
this.productsToSell.add(assignSimpleRole);
}

/** interaction method on the context. calling this, starts the
interaction */
public void doSale(){

System.out.println("At start of interaction, the till has this much
cash: " + accountant.getCashBalance());

//start interaction
accountant.createAccountEntries(productsToSell);

System.out.println("DCI Complete. Till now has this much cash: " +
accountant.getCashBalance());
}

/** this is the role which an object can play in the sales context.
in this
* context, the accountant creates some accounting records */
@Role(contextClass=SalesContext.class,
implementationClass=Accountant.AccountantImpl.class)
public interface Accountant {

void createAccountEntries(List<Product> products);
BigDecimal getCashBalance();
void setCashBalance(BigDecimal newBalance);

//the implementation of the methods above which are not in the data
class
static class AccountantImpl {

@Self
public Accountant self;

public void createAccountEntries(List<Product> products){
BigDecimal total = BigDecimal.ZERO;
for(Product p : products){
total = total.add(p.getPrice());
System.out.println("Creating an accounting record for: " + p);
}

self.setCashBalance(self.getCashBalance().add(total));
}
}
}

/** a simple role, describing the products we use in the sales
context */
@Role(contextClass=SalesContext.class)
public interface Product {
BigDecimal getPrice();
}
}

----- test class to run the sample -----

package ch.maxant.till.dci;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import ch.maxant.till.common.data.Book;
import ch.maxant.till.common.data.Till;

public class TillRunner {

public static void main(String[] args) {

List<Book> booksToSell = new ArrayList<Book>();
Book b = new Book("JQuery in Action, Second Edition",
"978-1-935182-32-10", new BigDecimal("44.99"));
booksToSell.add(b);
b = new Book("BPM Basics for Dummies", "978-0-470-28571-8", new
BigDecimal("26.95"));
booksToSell.add(b);

Till till = new Till(new BigDecimal("200.00"));

SalesContext c = new SalesContext(till);

for(Book book : booksToSell){
c.addProduct(book);
}
c.doSale();

System.out.println("Done");
}
}





----- data classes are identical to those listed above -----

James O. Coplien

unread,
Aug 20, 2011, 5:31:46 PM8/20/11
to object-co...@googlegroups.com

On Aug 20, 2011, at 10:28 , Ant Kutschera wrote:

> The only thing I can think of at the moment, is that the Context
> depends explicitly on specific data types (Book and Till).

Restricted OO.

Ant Kutschera

unread,
Aug 20, 2011, 5:34:38 PM8/20/11
to object-composition
PPS - if you look at my solution, rather than the OT/J solution, you
can see something interesting... namely that in my context, the
accountant role depends only on Product, and not Book. That means,
that sometime in the future, my Till could sell "Animals" as long as
they had the method "getPrice()".

The OT/J solution seems to be too restrictive, requiring me to first
change my data classes, before i can use my context&roles.

Stephan Herrmann

unread,
Aug 20, 2011, 8:22:54 PM8/20/11
to object-composition
Hi Ant,

On Aug 20, 11:28 pm, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> OK, I've implemented the following, and it all feels very good.
>
> The only thing I can think of at the moment, is that the Context
> depends explicitly on specific data types (Book and Till).

you didn't tell me, that you want to sell animals, so I assumed the
cashier works in a book store, not at a pet shop :). For a book store
I consider declaring an explicit "playedBy Book" as an advantage, not
as a problem.

You sound like you favor structural subtyping over nominal subtyping?
I see good reasons for either, but with Java as the base language
adding structural subtyping may introduce quite some surprises, which
I prefer to avoid. In other contexts other arguments may prevail.
Indeed: OT/J is a strongly typed language, I want the compiler to give
all the possible help.

That said, we can still add the desired flexibility. Several solutions
exist, but let's for now focus on a situation where each context is
limited to one kind of product but different contexts can be dedicated
to different kinds of products. Sounds fair for a start?

In order to make SalesContext independent of Book we just have to
remove a few things:

Role Product should no longer declare "playedBy Book". This also means
we have to turn the callout binding into an abstract method
declaration, which renders role Product abstract, like so:

protected abstract class Product {
abstract protected BigDecimal getPrice();
}

With an abstract role it is only fair to also declare the team
abstract. Also the method addProduct should no longer say "Book as
Product", but simply "Product", here's the result with no mentioning
of "Book":
------------------------------------------
package ch.maxant.till.otj;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

//"base", so that it is only possible to use till with the "playedBy"
keyword
import base ch.maxant.till.common.data.Till;

/**
* this is the sales context in which a till creates accounting
records
*/
public abstract team class SalesContext {

private final Accountant accountant;
private List<Product> productsToSell;

public SalesContext(Till as Accountant till) {
this.accountant = till;
productsToSell = new ArrayList<Product>();
}

/** setup method on context, to be called before starting the
interaction. */
public void addProduct(Product product){
this.productsToSell.add(product);
}

/** interaction method on the context. calling this, starts the
interaction */
public void doSale(){
System.out.println("At start of interaction, the till has this
much cash: " + accountant.getCashBalance());

//start interaction
accountant.createAccountEntries(productsToSell);

System.out.println("OT/J Complete. Till now has this much
cash: " + accountant.getCashBalance());
}

/** this is the role which a till can play in the sales context.
in this
* context, the till creates some accounting records */
protected class Accountant playedBy Till {

//Accountant can call setCashBalance on the Till
private void setCashBalance(BigDecimal newBalance) -> void
setCashBalance(BigDecimal newBalance);
protected BigDecimal getCashBalance() -> BigDecimal
getCashBalance();

protected void createAccountEntries(List<Product> products){
BigDecimal total = BigDecimal.ZERO;
for(Product p : products){
total = total.add(p.getPrice());
System.out.println("Creating an accounting record for:
" + p);
}

setCashBalance(getCashBalance().add(total));
}
}

/** a simple role, describing the products we use in the sales
context */
protected abstract class Product {
abstract protected BigDecimal getPrice();
}
}
------------------------------------------
Now we have the minimal definition of a sales context. How do we get
this bound to the player Book? In OT/J we typically do this with team
inheritance:

------------------------------------------
public team class BookSalesContext extends SalesContext {

public BookSalesContext(Till as Accountant till) {
super(till);
}

public void addProduct(Book as Product book){
super.addProduct(book);
}

@Override
protected class Product playedBy Book {
getPrice -> getPrice;
toString => toString;
}
}
------------------------------------------
That's what we call a connector (see http://wiki.eclipse.org/OTPattern/Connector).
No implementation, just binding the existing context to also existing
data classes.

The constructor we only have to repeat because this is Java. The
addProduct method is now enhanced to show the signature we initially
had. Finally, we fill in the missing details of role Product: the
binding to its player and the individual callout bindings. That's all!

To see the flexibility in action let's assume we also have this data
class:
------------------------------------------
package ch.maxant.till.common.data;


public class Animal {
private int value;
private String name;

public Animal(String name, int value) {
this.name = name;
this.value = value;
}

public int getValue() {
return value;
}

public String toString() {
return name;
}
}
------------------------------------------
And here's another connector:
------------------------------------------
public team class AnimalSalesContext extends SalesContext {

public AnimalSalesContext(Till as Accountant till) {
super(till);
}

public void addProduct(Animal as Product product) {
super.addProduct(product);
}

@Override
protected class Product playedBy Animal {

BigDecimal getPrice() -> int getValue()
with { result <- new BigDecimal(result) }

toString => toString;
}
}
------------------------------------------
I deliberately introduced some incompatibility: since the developers
of Book and Animal didn't know that these classes would ever play the
same role, the signatures differ in name (getPrice vs. getValue) and
type (BigDecimal vs. int). This difference is bridged by the callout
binding, where a parameter mapping is added to convert from int to
BigDecimal.

With these classes in place we can extend the TillRunner, so we sell
some books plus an animal:
------------------------------------------
package ch.maxant.till.otj;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import ch.maxant.till.common.data.Animal;
import ch.maxant.till.common.data.Book;
import ch.maxant.till.common.data.Till;

public class TillRunner {

public static void main(String[] args) {

List<Book> booksToSell = new ArrayList<Book>();
Book b = new Book("JQuery in Action, Second Edition",
"978-1-935182-32-10", new BigDecimal("44.99"));
booksToSell.add(b);
b = new Book("BPM Basics for Dummies", "978-0-470-28571-8",
new BigDecimal("26.95"));
booksToSell.add(b);

Till till = new Till(new BigDecimal("200.00"));

// sell some books:
BookSalesContext c = new BookSalesContext(till);

for (Book book : booksToSell) {
c.addProduct(book);
}
c.doSale();

// sell an animal:
AnimalSalesContext c2 = new AnimalSalesContext(till);
c2.addProduct(new Animal("Jumbo", 1000000));
c2.doSale();

System.out.println("Done");
}
}
------------------------------------------

Did you notice: Book and Animal have no common supertype, neither
nominally nor structurally. Animal has no getPrice() method. Still
role Product unifies these incompatible types. This is more powerful
than structural subtyping and still all this is 100% statically
checked.

Enough for now?
cheers,
Stephan

Stephan Herrmann

unread,
Aug 20, 2011, 8:32:32 PM8/20/11
to object-composition
On Aug 20, 11:34 pm, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> The OT/J solution seems to be too restrictive, requiring me to first
> change my data classes, before i can use my context&roles.

Nobody wants you to change your data classes :)

I'm happy how far you got in the first iterations, using a minimal set
of 4 concepts.

For more ambitious goals we need a few more concepts. So for fully
decoupling a context from any player use an abstract team with unbound
roles.
Then use a connector to bind this to specific players, this is how we
teach the general context how to handle new kinds of players.

What's next?
Stephan



Ant Kutschera

unread,
Aug 21, 2011, 2:11:44 PM8/21/11
to object-composition
Good cryptic reply. Do you fancy expanding on this?

Ant Kutschera

unread,
Aug 21, 2011, 4:15:14 PM8/21/11
to object-composition
Hi Stephan,

Very good. :-)

The background as to why I want decouple my specific data types from
say a sales context, is because I work for clients who for example
have 500 applications and batches running, all in the same business
unit, with life times up to 20 years (the newest, perhaps even
longer). So you said I didn't tell you I wanted to sell animals - I
didn't know I wanted to sell them. I have no idea what they will
invent in 19 years time that my customer might want to process in a
sales context...

I cannot have a base class, from which anything which is used in the
sales context inherits, because a change to that base class means I
have to regression test all software using that class, and that is
really expensive (typically millions of USD).

So your solution is very good - thanks.

The fact that I have to create a context subclass for every type of
product I want to sell, doesn't worry me, because I have to do that
today with my services solutions - I write a mapper for every
application that wants to send stuff to the sales service.

The code I posted with my BehaviourInjector is "interesting", because
it doesn't require a mapper or sub-classes. But it's turns statically
typed Java into a quasi-dynamic language, and you can only work out if
you made a mistake, by testing it. Trygve (I think) said in one of
the videos, that's bad. My own experience of my own library is a
little bad (every time I use it, I waste a few minutes because at
runtime something doesn't work). Your solution is good, like you say,
because the compiler tells you when you make a mistake.

Thanks for your efforts - I don't have any further questions now.

Unless... you want to check out the other thread where I ask about how
to identify roles? :-)

Ant


On Aug 21, 2:32 am, Stephan Herrmann <stephan.herrm...@onlinehome.de>
wrote:

Ant Kutschera

unread,
Aug 21, 2011, 5:18:43 PM8/21/11
to object-composition
On Aug 21, 2:32 am, Stephan Herrmann <stephan.herrm...@onlinehome.de>
wrote:
> What's next?

OK - one more question, in case you are bored :-)

What if I want to sell multiple product types all in the same sales
context? Your solution so far means I have to create two contexts.
The use case could be "the customer brings a basket full of products
to the till" (they have ones large enough for elephants).

With my BehaviourInjector, I keep adding objects to the context before
I call "doSale()", and as long as they fulfil the role contract
(methods required on data classes by the context/roles), then I have
no problem.

Ant

James O. Coplien

unread,
Aug 22, 2011, 1:06:01 PM8/22/11
to object-co...@googlegroups.com

On Aug 21, 2011, at 7:11 , Ant Kutschera wrote:

> Good cryptic reply. Do you fancy expanding on this?


Probably not. I was building on the fact that you announced that you now understood Restricted OO. Your antecedent mail gave an example of it.

Stephan Herrmann

unread,
Aug 22, 2011, 3:15:46 PM8/22/11
to object-composition
On Aug 21, 11:18 pm, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> OK - one more question, in case you are bored :-)
>
> What if I want to sell multiple product types all in the same sales
> context? Your solution so far means I have to create two contexts.
> The use case could be "the customer brings a basket full of products
> to the till" (they have ones large enough for elephants).

The first step to achieving this is really simple: place all the bound
roles into the same context. Instead of team inheritance use role
inheritance: an abstract role Product as before followed by these
specializations:

protected class BookForSale extends Product playedBy Book {
getPrice -> getPrice;
toString -> toString;
}
protected class AnimalForSale extends Product playedBy Animal {
BigDecimal getPrice() -> int getValue()
with { result <- new BigDecimal(result) }
toString -> toString;
}

Anything surprising so far?

What else do we need to change? Everything inside SalesContext can
safely continue to refer to the abstract role Product. But where do we
put the specialized "addProduct(Book as Product book)" method, and its
pendant for animals?
Answer: we don't, with a little twist we can use generics to write
only *one* addProduct method:

public <B> void addProduct(B as Product product){
this.productsToSell.add(product);
}

We simply introduce a generic parameter B as a placeholder for any
role player, so clients can pass books and animals as arguments, and
the "as" will translate them to products. Unfortunately the compiler
can't allow the above, because a free type parameter B could be
instantiated with *any* type, so it would seem legal to invoke
addProduct(new SkyScraper()), which won't work because we don't have a
SkyScraperForSale role.
To make it work we only need to add one little type constraint: we
don't accept *any* B but only "B base Product":

public <B base Product> void addProduct(B as Product product){
this.productsToSell.add(product);
}

This type bound restricts B to all types that are role players (OT/J-
speak: "base classes") of Product. Now the compiler will tell you that
addProduct(new Book()) is OK, the same for animals, but everything
else is a compile time error.

If you want a name for the type "B base Product": it is the *union* of
all types X for which a Product role exists (i.e., a role that is a
subtype of Product and that declares "playedBy X").

For your convenience here's the complete context:
----------------------------------------------------------
package ch.maxant.till.otj;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;


//"base", so that it is only possible to use till with the "playedBy"
keyword
import base ch.maxant.till.common.data.Animal;
import base ch.maxant.till.common.data.Book;
import base ch.maxant.till.common.data.Till;

/**
* this is the sales context in which a till creates accounting
records
*/
public team class SalesContext {

private final Accountant accountant;
private List<Product> productsToSell;

public SalesContext(Till as Accountant till) {
this.accountant = till;
productsToSell = new ArrayList<Product>();
}

/** setup method on context, to be called before starting the
interaction. */
public <B base Product> void addProduct(B as Product product){
this.productsToSell.add(product);
}

/** interaction method on the context. calling this, starts the
interaction */
public void doSale(){
System.out.println("At start of interaction, the till has this
much cash: " + accountant.getCashBalance());

//start interaction
accountant.createAccountEntries(productsToSell);

System.out.println("OT/J Complete. Till now has this much
cash: " + accountant.getCashBalance());
}

/** this is the role which a till can play in the sales context.
in this
* context, the till creates some accounting records */
protected class Accountant playedBy Till {

//Accountant can call setCashBalance on the Till
private void setCashBalance(BigDecimal newBalance) -> void
setCashBalance(BigDecimal newBalance);
protected BigDecimal getCashBalance() -> BigDecimal
getCashBalance();

protected void createAccountEntries(List<Product> products){
BigDecimal total = getCashBalance();
for(Product p : products){
total = total.add(p.getPrice());
System.out.println("Creating an accounting record for:
" + p);
}

setCashBalance(total);
}
}

/** a simple role, describing the products we use in the sales
context */
protected abstract class Product {
@Override
public abstract String toString();
protected abstract BigDecimal getPrice();
}
// bind this role to some concrete data classes:
protected class BookForSale extends Product playedBy Book {
getPrice -> getPrice;
toString -> toString;
}
protected class AnimalForSale extends Product playedBy Animal {
BigDecimal getPrice() -> int getValue()
with { result <- new BigDecimal(result) }
toString -> toString;
}
}
----------------------------------------------------------
and the client code for a mixed sale:
----------------------------------------------------------
List<Book> booksToSell = new ArrayList<Book>();
Book b = new Book("JQuery in Action, Second Edition",
"978-1-935182-32-10", new BigDecimal("44.99"));
booksToSell.add(b);
b = new Book("BPM Basics for Dummies", "978-0-470-28571-8",
new BigDecimal("26.95"));
booksToSell.add(b);

Till till = new Till(new BigDecimal("200.00"));

SalesContext c = new SalesContext(till);

for (Book book : booksToSell) {
c.addProduct(book);
}
c.addProduct(new Animal("Jumbo", 1000000));
c.doSale();

System.out.println("Done");
----------------------------------------------------------

Ah, now we're back at putting all the dependencies on data classes in
the same file? If you think that's a show stopper, simply put the
roles in separate files, see
http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.objectteams.otdt.doc%2Fguide%2Fotjld%2Fdef%2Fs1.html%23s1.2.5
("1.2.5 File structure").

> With my BehaviourInjector, I keep adding objects to the context before
> I call "doSale()", and as long as they fulfil the role contract
> (methods required on data classes by the context/roles), then I have
> no problem.

If that's what you want, I won't stop you. And with the above hints
you'll know statically whether you have a problem or not.

BTW: from looking at your injector and the way it uses reflection for
type checking it probably won't work with type parameters in method
signatures, right?

cheers,
Stephan

James O. Coplien

unread,
Aug 22, 2011, 4:31:46 PM8/22/11
to object-co...@googlegroups.com

On Aug 22, 2011, at 8:15 , Stephan Herrmann wrote:

> Instead of team inheritance use role
> inheritance: an abstract role Product as before followed by these
> specializations:


DCI disallows this. It destroys the readability of code. You're back to the old problems with polymorphism. If you have role inheritance, why even bother separating out the roles? Just go ahead and use Unconstrained OO.

Ant Kutschera

unread,
Aug 22, 2011, 4:55:29 PM8/22/11
to object-composition
On Aug 22, 10:31 pm, "James O. Coplien" <jcopl...@gmail.com> wrote:
> DCI disallows this. It destroys the readability of code. You're back to the old problems with polymorphism. If you have role inheritance, why even bother separating out the roles? Just go ahead and use Unconstrained OO.

Easy tiger :-)

The inheritance here is simply to decouple the context/interaction
from specific data types Book and Animal.

I have spent time critically looking at the code and considering how
hard a time a reviewer could have. It isn't as bad as you are making
out.

Ant Kutschera

unread,
Aug 22, 2011, 4:58:34 PM8/22/11
to object-composition
> BTW: from looking at your injector and the way it uses reflection for
> type checking it probably won't work with type parameters in method
> signatures, right?

I never tried, but I would guess not.

Your solution yet again is very good and does what I need it to (I'm
99% sure, I will stick it in Eclipse shortly).


I was comparing it to a wrapper solution today and came to realise
that I have to always explicitly use a call out for things like
"equals", "hashCode" and "toString".

To avoid having to repeat code over and over, is there any ways to do
this:

* => *

:-)

Stephan Herrmann

unread,
Aug 22, 2011, 6:20:50 PM8/22/11
to object-composition
I was preparing a longer answer to Cope's post, but I like this one
a lot better than mine:

On Aug 22, 10:55 pm, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> The inheritance here is simply to decouple the context/interaction
> from specific data types Book and Animal.
>
> I have spent time critically looking at the code and considering how
> hard a time a reviewer could have. It isn't as bad as you are making
> out.

simple things said simply,
thanks,
Stephan

Stephan Herrmann

unread,
Aug 23, 2011, 9:56:00 AM8/23/11
to object-composition
On Aug 22, 10:58 pm, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> I was comparing it to a wrapper solution today and came to realise
> that I have to always explicitly use a call out for things like
> "equals", "hashCode" and "toString".

Doing so might be the safest solution, although in normal use OT/J
even avoids the need. E.g.,: if in some Data class, i.e., outside the
context, you have this set:

public class TodoList {
public static Set<Book> booksIShouldRead; // excuse static,
just keeping the example simple
}

and within your context you do s.t. like this

void remember(BookForSale book) {
TodoList.booksIShouldRead.add(book);
}

the compiler can see that you are trying to compare apples and
oranges.
To avoid trouble and to make the snippet type-correct (a BookForSale
is *not* a subtype of Book), a "lowering" translation is automatically
inserted, which means you actually store the Book, not the
BookForSale, in the set. More like that can be found in the paper
"Demystifying Object Schizophrenia" mentioned before. The general rule
is: inside the context you only have the roles, outside you only have
data objects => you cannot normally mix both kinds in one collection
etc.

> To avoid having to repeat code over and over, is there any ways to do
> this:
>
> * => *
>
> :-)

A callout-for-all kind-of spoils the decoupling we have achieved with
explicit callout bindings. This is not supported in OT/J. However, in
some situations you may let the compiler *infer* certain callout
bindigs (http://help.eclipse.org/indigo/index.jsp?topic=
%2Forg.eclipse.objectteams.otdt.doc%2Fguide%2Fotjld%2Fdef%2Fs3.html
%23s3.1.j "3.1.j inferred callout").
Note, that callout inference is per default configure as "Error".

For example, with this role

abstract protected class Product {
public abstract BigDecimal getPrice();
}

you may let the compiler infer the straight callout like this:

@SuppressWarnings("inferredcallout")
protected class BookForSale extends Product playedBy Book {}

(Indeed I like to configure inferred callout to "Warning", so the
@SuppressWarnings annotation will mark all occurrences in the source
code).

Two caveats: for existing methods like toString, equals, hashCode
you'd have to ensure that they are inherited as abstract methods (not
sure if it's worth the hassle). Second: recently a few bugs relating
to callout inference were found & fixed in the compiler. So if you
want to experiment with inferred callouts I advise you to either wait
for the 2.0.1 release (Indigo SR1 on Sept. 23) or use a release
candidate (I can provide the URL on request).

cheers,
Stephan

Stephan Herrmann

unread,
Aug 23, 2011, 10:01:06 AM8/23/11
to object-composition
> in some situations you may let the compiler *infer* certain callout bindigs

Line breaks broke this link, trying again (section 3.1.j of the
OTJLD):

http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.objectteams.otdt.doc%2Fguide%2Fotjld%2Fdef%2Fs3.html%23s3.1.j

Ant Kutschera

unread,
Aug 23, 2011, 4:45:55 PM8/23/11
to object-composition
On Aug 23, 3:56 pm, Stephan Herrmann <stephan.herrm...@onlinehome.de>
wrote:
> The general rule
> is: inside the context you only have the roles, outside you only have
> data objects => you cannot normally mix both kinds in one collection
> etc.

Yeah, I noticed that. On the dci-evolution board, there is a hot
debate about the use of wrappers to implement DCI, and the big
argument against them is object schizophrenia. I added a note to this
effect, as I feel it does help to avoid the problems greatly.

James O. Coplien

unread,
Aug 25, 2011, 1:20:03 PM8/25/11
to object-co...@googlegroups.com
So if you derive roles both B and C from A, and inject A into object O, how many copies of A's methods are there in O? This actually makes a difference in most languages. CLOS has wrappers and whoppers to disambiguate their access. Even if the roles have no member data, if any of the methods has a static member, you're probably hosed.

I find it much better to separate the design into three roles. Inject whatever roles need to be injected into the object. You would inject both the "base role" and one (or more) of the derived roles into the object. This ensures, among other things, that there is a single copy of the "base" role in the object.

I think it's a tossup on code readability and predictability, though there could be interesting discussions around this issue.

Miaow.

Stephan Herrmann

unread,
Aug 28, 2011, 1:37:44 PM8/28/11
to object-composition
On Aug 25, 7:20 pm, James O. Coplien <jcopl...@gmail.com> wrote:
> So if you derive roles both B and C from A, and inject A into object O, how many copies of A's methods are there in O?

General answer: in OT/J we never have multiple copies of a method.
In order to give a more specific answer I'd like to better understand
why you suspect a problem here.

If you suspect problems due to deriving B and C from A, that is
regular inheritance (except we were using it with the self-imposed
restriction that B and C add no behavior). Why should that cause
issues?

If you suspect problems due to injecting A into O: well, that's not a
typical way of describing things in OT/J, we would more likely speak
of attaching an A to an O. Which makes me think both phrases - using
"inject" or "attach" - give away some implementation details that are
not necessary for the concepts at hand. Would you mind, for a second,
to just speak of "composing" A with O?

So when composing A and O, all code in A can only see declarations in
A, and code in O can only see declarations in O. Only the explicit
method bindings specify possible access across the boundary between
both parts. That's how we avoid ambiguity. Do you still see any
problems, difficulties?

> Even if the roles have no member data, if any of the methods has a static member, you're probably hosed.

If it were OT/C we *might* run into that kind of problem, but in a
language based on Java we have no static variables inside methods.
OTOH, even with member data in roles (which is possible in OT/J) the
language definition leaves no doubt about the effects, and those
effects cause no harm. But that discussion is probably irrelevant for
DCI.

> I find it much better to separate the design into three roles. Inject whatever roles need to be injected into the object. You would inject both the "base role" and one (or more) of the derived roles into the object.

For agreeing to the "better" part, I'd necessarily need to see a
problem with the inheritance-based solution, which I don't.
I assume with "three roles" you mean e.g. Product (A), BookForSale (B)
and AnimalForSale (C). If so, would you allow composing an
AnimalForSale role with an Animal *without* also adding a Product
role? I'd consider this as inconsistent (in the context of the Till
example) and therefor see no advantage in introducing this option.

> This ensures, among other things, that there is a single copy of the "base" role in the object.

So this solves a problem which I can't see in OT/J.

Of course I'd be happy to discuss any supposed counter examples
against my claim. The IDE, the language definition and plenty of
documentation are all available to help you construct examples. I
apologize for using Java as the host language :)

cheers,
Stephan

James O. Coplien

unread,
Aug 28, 2011, 2:01:54 PM8/28/11
to object-co...@googlegroups.com
On Aug 28, 2011, at 7:37 , Stephan Herrmann wrote:

> Do you still see any
> problems, difficulties?

This makes it even worse, because the implementation (reference rather than composition) makes it difficult to reconcile scope bindings (which must be dynamic) and therefore more difficult to flag problems.


> If it were OT/C we *might* run into that kind of problem

Can one use "might" in an OOPSLA paper?

Underwear hanging out.

Stephan Herrmann

unread,
Aug 30, 2011, 6:16:31 AM8/30/11
to object-composition
Do you really consider this as a response to my post?

We were discussing the pros and cons of using inheritance as a
composition mechanism, stimulated by an in example in OT/J.

Knowing about your strong dislike of any split-objects implementation
I made the offer to discuss the question of inheritance in a neutral
terminology (using neither "inject" nor "attach") and I think I have
shown that those issues can indeed be discussed in isolation. If you
accept this offer to separate concerns we can continue the discussion
about inheritance.

If you prefer a discussion about split-objects you're invited to
present an example that is specific enough so that it can be either
verified or falsified by others.

I'm open to anything -- except yelling at each other, but then I may
be over-interpreting your strong words, I'm not a native English
speaker.

James O. Coplien

unread,
Aug 30, 2011, 8:46:18 AM8/30/11
to object-co...@googlegroups.com

On Aug 30, 2011, at 12:16 , Stephan Herrmann wrote:

> If you prefer a discussion about split-objects you're invited to
> present an example that is specific enough so that it can be either
> verified or falsified by others.
>
> I'm open to anything -- except yelling at each other, but then I may
> be over-interpreting your strong words, I'm not a native English
> speaker.

Probably. We can go to a neutral third language if you like, such as French or Danish. Or how about Ruby? Translate this to Java using wrappers and I think you'll have your wish for our discussion.

#!/usr/bin/env ruby
# Example in Ruby – Dijkstra's algorithm in a programming style
# Modified and simplified for a Manhattan geometry with 3 roles
#
#
# Demonstrates an example where:
# - objects of class Node play several roles simultaneously
# (albeit spread across Contexts: a Node can
# play the CurrentIntersection in one Context and an Eastern or Southern
# Neighbor in another)
# - stacked Contexts (to implement recursion)
# - mixed access of objects of Node through different
# paths of role elaboration (the root is just a node,
# whereas others play roles)
# - there is a significant pre-existing data structure called
# a Map which contains the objects of instance. Where DCI
# comes in is to ascribe roles to those objects and let
# them interact with each other to evaluate the minimal
# path through the network
# - true to core DCI we are almost always concerned about
# what happens between the objects (paths and distance)
# rather than in the objects themselves (which have
# relatively uninteresting properties like "name")
# - equality of nodes is not identity, and several
# nodes compare equal with each other by standard
# equality (eql?)
# - returns references to the original data objects
# in a vector, to describe the resulting path
#
# There are some curiousities
# - EastNeighbor and SouthNeighbor were typographically equivalent,
# so I folded them into a single role: Neighbor. That type still
# serves the two original roles
# - Roles are truly scoped to the use case context
# - The Map and Distance_labeled_graph_node roles have to be duplicated in two Contexts
# - Node inheritance is replaced by injecting two roles
# into the object
# - Injecting some roles causes data objects to take on new
# data members. I can work around this by keeping the data
# in a separate associative vector, but this seems the
# right "Ruby way"
# - There is an intentional call to distance_between while the
# Context is still extant, but outside the scope of the
# Context itself. Should that be legal?

# Global boilerplate

Pair = Struct.new(:from, :to)
def infinity; return (2**(0.size * 8 -2) -1) end

module ContextAccessor
def context
Thread.current[:context]
end

def context=(ctx)
Thread.current[:context] = ctx
end

def execute_in_context
old_context = self.context
self.context = self
yield
self.context = old_context
end
end


#
# Consider street corners on a Manhattan grid. We want to find the
# minimal path from the most northeast city to the most
# southeast city. Use Dijstra's algorithm
#

# Data class

class Node
attr_reader :name
def initialize(n); @name = n end
def eql? (another_node)
# Nodes are == equal if they have the same name
return name == another_node.name
end
def == (another_node)
# Equality used in the Map algorithms is object identity
super
end
end


# This is the main Context for shortest path calculation

class CalculateShortestPath
# Housekeeping crap

include ContextAccessor

# These are handles to internal housekeeping arrays set up in initialize

def unvisited; @unvisited end
def pathTo; @pathTo end
def east_neighbor; @east_neighbor end
def south_neighbor; @south_neighbor end
def path; @path end
def map; @map end
def current; @current end
def destination; @destination end

# Initialization

def rebind(origin_node, geometries)
@current = origin_node
@map = geometries
@map.extend Map
@current.extend CurrentIntersection
@east_neighbor = map.east_neighbor_of(origin_node)
geometries.nodes.each {
|node|
node.extend Distance_labeled_graph_node
}
if @east_neighbor != nil
@east_neighbor.extend Neighbor
end
@south_neighbor = map.south_neighborOf(origin_node)
if @south_neighbor != nil
@south_neighbor.extend Neighbor
end
end

# public initialize. It's overloaded so that the public version doesn't
# have to pass a lot of crap; the initialize method takes care of
# setting up internal data structures on the first invocation. On
# recursion we override the defaults

def initialize(origin_node, target_node, geometries, path_vector = nil, unvisited_hash = nil, pathto_hash = nil)
@destination = target_node

rebind(origin_node, geometries)

# This has to come after rebind is done
if path_vector.nil?

# This is the fundamental data structure for Dijkstra's algorithm, called
# "Q" in the Wikipedia description. It is a boolean hash that maps a
# node onto false or true according to whether it has been visited
@unvisited = Hash.new

# These initializations are directly from the description of the algorithm
geometries.nodes.each { |node| @unvisited[node] = true }
@unvisited.delete(origin_node)
map.nodes.each { |node| node.set_tentative_distance_to(infinity) }
origin_node.set_tentative_distance_to(0)

# The path array is kept in the outermost context and serves to store the
# return path. Each recurring context may add something to the array along
# the way. However, because of the nature of the algorithm, individual
# Context instances don't deliver "partial paths" as partial answers.
@path = Array.new

# The pathTo map is a local associative array that remembers the
# arrows between nodes through the array and erases them if we
# re-label a node with a shorter distance
@pathTo = Hash.new

else
@unvisited = unvisited_hash
@path = path_vector
@pathTo = pathto_hash
end

execute
end


# There are four roles in the algorithm:
#
# CurrentIntersection (@current)
# EastNeighbor, which lies DIRECTLY to the east of CurrentIntersection (@east_neighbor)
# SouthernNeighbor, which is DIRECTLy to its south (@south_neighbor)
# Destination, the target node (@destination)
#
# We also add a role of Map (@map) as the oracle for the geometry
#
# The algorithm is straight from Wikipedia:
#
# http://en.wikipedia.org/wiki/Dijkstra's_algorithm
#
# and reads directly from the distance method, below

module Distance_labeled_graph_node

# NOTE: This role creates a new data member in the node into
# which it is injected. An alernative implementation would
# be to use a separate associative array

def tentative_distance; @tentative_distance_value end
def set_tentative_distance_to(x); @tentative_distance_value = x end
end

module CurrentIntersection

# Access to roles and other Context data

include ContextAccessor
def unvisited; context.unvisited end
def south_neighbor; context.south_neighbor end
def east_neighbor; context.east_neighbor end

def unvisited_neighbors
retval = Array.new
if south_neighbor != nil
if unvisited[south_neighbor] == true; retval << south_neighbor end
end
if east_neighbor != nil
if unvisited[east_neighbor] == true; retval << east_neighbor end
end
return retval
end
end

# This module serves to provide the methods both for the east_neighbor and south_neighbor roles

module Neighbor
include ContextAccessor

def relable_node_as(x)
if x < self.tentative_distance; self.set_tentative_distance_to(x); return true
else return false end
end
end

# "Map" as in cartography rather than Computer Science...
#
# Map is technically a role from the DCI perspective. The role
# in this example is played by an object representing a particular
# Manhattan geometry

module Map
include ContextAccessor

def distance_between(a, b)
return @distances[Pair.new(a, b)]
end

# These two functions presume always travelling
# in a southern or easterly direction

def next_down_the_street_from(x)
return east_neighbor_of(x)
end

def next_along_the_avenue_from(x)
return south_neighborOf(x)
end
end

# This is the method that does the work. Called from initialize

def execute
execute_in_context do
# Calculate tentative distances of unvisited neighbors
unvisited_neighbors = current.unvisited_neighbors

if unvisited_neighbors != nil
unvisited_neighbors.each { |neighbor|
if neighbor.relable_node_as(current.tentative_distance + map.distance_between(current, neighbor))
pathTo[neighbor] = current
end
}
end

unvisited.delete(current)

# Are we done?

if unvisited.size == 0
save_path(@path)
else

# The next current node is the one with the least distance in the
# unvisited set

selection = nearest_unvisited_node_to_target

# Recur
CalculateShortestPath.new(selection, destination, map, path, unvisited, pathTo)
end
end
end

def nearest_unvisited_node_to_target
min = infinity
selection = nil
unvisited.each_key { |intersection|
if unvisited[intersection]
if intersection.tentative_distance < min
min = intersection.tentative_distance
selection = intersection
end
end
}
return selection
end

def each
path.each { |node| yield node }
end

# This method does a simple traversal of the data structures (following pathTo)
# to build the directed traversal vector for the minimum path

def save_path(pathVector)
node = destination
begin
pathVector << node
node = pathTo[node]
end while node != nil
end

end

# This is the main Context for shortest distance calculation

class CalculateShortestDistance
include ContextAccessor

def path; return @path end

module Map
include ContextAccessor

def distance_between(a, b)
return @distances[Pair.new(a, b)]
end

# These two functions presume always travelling
# in a southern or easterly direction

def next_down_the_street_from(x)
return east_neighbor_of(x)
end

def next_along_the_avenue_from(x)
return south_neighborOf(x)
end
end

module Distance_labeled_graph_node
#NOTE: This creates a new data member in the node
def tentative_distance; @tentative_distance_value end
def set_tentative_distance_to(x); @tentative_distance_value = x end
end

def rebind(origin_node, geometries)
@current = origin_node
@destination = geometries.destination
@map = geometries
@map.extend Map
@map.nodes.each {
|node|
node.extend Distance_labeled_graph_node
}
end

def initialize(origin_node, target_node, geometries)
rebind(origin_node, geometries)
@current.set_tentative_distance_to(0)
@path = CalculateShortestPath.new(@current, @destination, @map).path
end

def distance
retval = 0
previous_node = nil
path.reverse_each {
|node|
if previous_node.nil?
retval = 0
else
retval += @map.distance_between(previous_node, node)
end
previous_node = node
}
return retval
end
end

class ManhattanGeometry1
def initialize
@nodes = Array.new
@distances = Hash.new

names = [ "a", "b", "c", "d", "a", "b", "g", "h", "i"]

3.times { |i|
3.times { |j| @nodes << Node.new(names[(i*3)+j]) }
}

# Aliases to help set up the grid. Grid is of Manhattan form:
#
# a - 2 - b - 3 - c
# | | |
# 1 2 1
# | | |
# d - 1 - e - 1 - f
# | |
# 2 4
# | |
# g - 1 - h - 2 - i
#
@a = @nodes[0]
@b = @nodes[1]
@c = @nodes[2]
@d = @nodes[3]
@e = @nodes[4]
@f = @nodes[5]
@g = @nodes[6]
@h = @nodes[7]
@i = @nodes[8]

9.times { |i|
9.times { |j|
@distances[Pair.new(@nodes[i], @nodes[j])] = infinity
}
}

@distances[Pair.new(@a, @b)] = 2
@distances[Pair.new(@b, @c)] = 3
@distances[Pair.new(@c, @f)] = 1
@distances[Pair.new(@f, @i)] = 4
@distances[Pair.new(@b, @e)] = 2
@distances[Pair.new(@e, @f)] = 1
@distances[Pair.new(@a, @d)] = 1
@distances[Pair.new(@d, @g)] = 2
@distances[Pair.new(@g, @h)] = 1
@distances[Pair.new(@h, @i)] = 2
@distances[Pair.new(@d, @e)] = 1
@distances.freeze


@next_down_the_street_from = Hash.new
@next_down_the_street_from[@a] = @b
@next_down_the_street_from[@b] = @c
@next_down_the_street_from[@d] = @e
@next_down_the_street_from[@e] = @f
@next_down_the_street_from[@g] = @h
@next_down_the_street_from[@h] = @i
@next_down_the_street_from.freeze

@next_along_the_avenue_from = Hash.new
@next_along_the_avenue_from[@a] = @d
@next_along_the_avenue_from[@b] = @e
@next_along_the_avenue_from[@c] = @f
@next_along_the_avenue_from[@d] = @g
@next_along_the_avenue_from[@f] = @i
@next_along_the_avenue_from.freeze
end

def east_neighbor_of(a); @next_down_the_street_from[a] end
def south_neighborOf(a); @next_along_the_avenue_from[a] end

def root; return @a end
def destination; return @i end
def nodes; return @nodes end
end

class ManhattanGeometry2
def initialize
@nodes = Array.new
@distances = Hash.new

names = [ "a", "b", "c", "d", "a", "b", "g", "h", "i", "j", "k"]

11.times { |j| @nodes << Node.new(names[j]) }

# Aliases to help set up the grid. Grid is of Manhattan form:
#
# a - 2 - b - 3 - c - 1 - j
# | | | |
# 1 2 1 |
# | | | |
# d - 1 - e - 1 - f 1
# | | |
# 2 4 |
# | | |
# g - 1 - h - 2 - i - 2 - k
#
@a = @nodes[0]
@b = @nodes[1]
@c = @nodes[2]
@d = @nodes[3]
@e = @nodes[4]
@f = @nodes[5]
@g = @nodes[6]
@h = @nodes[7]
@i = @nodes[8]
@j = @nodes[9]
@k = @nodes[10]

11.times { |i|
11.times { |j|
@distances[Pair.new(@nodes[i], @nodes[j])] = infinity
}
}

@distances[Pair.new(@a, @b)] = 2
@distances[Pair.new(@b, @c)] = 3
@distances[Pair.new(@c, @f)] = 1
@distances[Pair.new(@f, @i)] = 4
@distances[Pair.new(@b, @e)] = 2
@distances[Pair.new(@e, @f)] = 1
@distances[Pair.new(@a, @d)] = 1
@distances[Pair.new(@d, @g)] = 2
@distances[Pair.new(@g, @h)] = 1
@distances[Pair.new(@h, @i)] = 2
@distances[Pair.new(@d, @e)] = 1
@distances[Pair.new(@c, @j)] = 1
@distances[Pair.new(@j, @k)] = 1
@distances[Pair.new(@i, @k)] = 2
@distances.freeze


@next_down_the_street_from = Hash.new
@next_down_the_street_from[@a] = @b
@next_down_the_street_from[@b] = @c
@next_down_the_street_from[@c] = @j
@next_down_the_street_from[@d] = @e
@next_down_the_street_from[@e] = @f
@next_down_the_street_from[@g] = @h
@next_down_the_street_from[@h] = @i
@next_down_the_street_from[@i] = @k
@next_down_the_street_from.freeze

@next_along_the_avenue_from = Hash.new
@next_along_the_avenue_from[@a] = @d
@next_along_the_avenue_from[@b] = @e
@next_along_the_avenue_from[@c] = @f
@next_along_the_avenue_from[@d] = @g
@next_along_the_avenue_from[@f] = @i
@next_along_the_avenue_from[@j] = @k
@next_along_the_avenue_from.freeze
end

def east_neighbor_of(a); @next_down_the_street_from[a] end
def south_neighborOf(a); @next_along_the_avenue_from[a] end

def root; return @a end
def destination; return @k end
def nodes; return @nodes end
end

# Test drivers

geometries = ManhattanGeometry1.new
path = CalculateShortestPath.new(geometries.root, geometries.destination, geometries)
print "Path is: "; path.each {|node| print "#{node.name} " }; print "\n"
puts "distance is #{CalculateShortestDistance.new(geometries.root, geometries.destination, geometries).distance}"

puts("")

geometries = ManhattanGeometry2.new
path = CalculateShortestPath.new(geometries.root, geometries.destination, geometries)
print "Path is: "
last_node = nil
path.each {
|node|
if last_node != nil; print " - #{geometries.distance_between(node, last_node)} - " end
print "#{node.name}"
last_node = node
};
print "\n"
puts "distance is #{CalculateShortestDistance.new(geometries.root, geometries.destination, geometries).distance}"

Ant Kutschera

unread,
Aug 30, 2011, 3:18:38 PM8/30/11
to object-composition
On Aug 30, 2:46 pm, "James O. Coplien" <jcopl...@gmail.com> wrote:
> module ContextAccessor
>   def context
>     Thread.current[:context]
>   end
>
>   def context=(ctx)
>     Thread.current[:context] = ctx
>   end
>
>   def execute_in_context
>     old_context = self.context
>     self.context = self
>     yield
>     self.context = old_context
>   end
> end

Hi James,

The yield above - can you explain it? I'm a Ruby Nuby :-)

I've Googled it, but can't quite work out entirely what code block it
yields to... There is also one at the bottom that also doesn't appear
to have a block.

>         module Distance_labeled_graph_node
>
>                 # NOTE: This role creates a new data member in the node into
>                 #       which it is injected. An alernative implementation would
>                 #       be to use a separate associative array
>
>                 def tentative_distance; @tentative_distance_value end

Yup, I'd say that's illegal, but let's go with it for now...

I'll deliver you a POJO Wrapper impl in Java shortly. Let's see if
it's as sexy, or if it's plagued by your identity crises... I am
guessing it will be problematic, but lets see what the concrete
solution delivers...

James O. Coplien

unread,
Aug 30, 2011, 3:41:47 PM8/30/11
to object-co...@googlegroups.com
Ant,

The explanation would be a bit long and arcane without some prior knowledge about blocks — which I'm guessing that you don't have, or you probably would have been doing something like this before.

In brief, it executes the block that was passed to execute_in_context as an argument. So:

execute_in_context { print " b c " }

def execute_in_context
print "a"
yield
print "d\n."
}

prints:

a b c d
.

More practically, you can ignore the code, and just know that it is used for stacking contexts. Replace this with whatever other context-stacking mechanism you are using.

I'll be thrilled to see the Java implementation. There are implementations going forward in Smalltalk and C++ in parallel as well. With your Java implementation representing the OT/J camp, we'll have three implementations to compare and discuss — CONCRETELY, for a change.

I consider the Ruby implementation to be sexy, yes. The C++ implementation is something only a mother could love and, well, I'm kind of like that as a C++ person. I haven't seen the Smalltalk implementation yet.

Wenig, Stefan

unread,
Aug 31, 2011, 3:24:36 AM8/31/11
to object-co...@googlegroups.com
Technical nitpicking: isn't execute_in_context missing some exception handling?
I'm not fluent in Ruby, in C# I'd write

[ThreadStatic] public static Context {get; private set;}

ExecuteInContext (Action act) {
oldContext = Context;
Context = this;
try {
a();
} finally {
Context = oldContext;
}
}

> -----Original Message-----
> From: object-co...@googlegroups.com [mailto:object-
> compo...@googlegroups.com] On Behalf Of James O. Coplien
> Sent: Tuesday, August 30, 2011 9:42 PM
> To: object-co...@googlegroups.com
> Subject: Re: Roles in Eclipse Indigo
>
> Ant,
>
> The explanation would be a bit long and arcane without some prior

> knowledge about blocks - which I'm guessing that you don't have, or you


> probably would have been doing something like this before.
>
> In brief, it executes the block that was passed to execute_in_context
> as an argument. So:
>
> execute_in_context { print " b c " }
>
> def execute_in_context
> print "a"
> yield
> print "d\n."
> }
>
> prints:
>
> a b c d
> .
>
> More practically, you can ignore the code, and just know that it is
> used for stacking contexts. Replace this with whatever other context-
> stacking mechanism you are using.
>
> I'll be thrilled to see the Java implementation. There are
> implementations going forward in Smalltalk and C++ in parallel as well.
> With your Java implementation representing the OT/J camp, we'll have

> three implementations to compare and discuss - CONCRETELY, for a

> --
> You received this message because you are subscribed to the Google
> Groups "object-composition" group.
> To post to this group, send email to object-
> compo...@googlegroups.com.
> To unsubscribe from this group, send email to object-
> composition...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/object-composition?hl=en.

James O. Coplien

unread,
Aug 31, 2011, 3:46:48 AM8/31/11
to object-co...@googlegroups.com
This is a pedagogical example designed to exemplify a particular set of concepts. One part of designing good examples is to make them readable. I have no client in mind who is going to use Dijkstra's algorithm to route Manhattan networks, so I have no heed to harden the code.

I worked 20 years on systems with seven 9s, and in any case exception handling is a toy with respect to code safety, reliability, and robustness. It was put into C++ only as a political chit to keep the library vendors happy and to keep them from having to interwork directly — they made it the language's problem. From there the same bad design propagated into other languages for sake of familiarity. Good reliability and fault tolerance have to be customized to the application and platform where they are being used. I don't know how to do that in a generic example.

In your code below, how do you know that returning to the old Context doesn't leave databases locked, or a missile on a mistaken trajectory? Error recovery is hard and doesn't come down to casually putting in a few catches and throws here and there. In a high-availability telecom system, such measures amount to 50% to 80% of the code mass.

Wenig, Stefan

unread,
Aug 31, 2011, 4:00:36 AM8/31/11
to object-co...@googlegroups.com
> This is a pedagogical example designed to exemplify a particular set of
> concepts. One part of designing good examples is to make them readable.
> I have no client in mind who is going to use Dijkstra's algorithm to
> route Manhattan networks, so I have no heed to harden the code.

Agreed



> I worked 20 years on systems with seven 9s, and in any case exception
> handling is a toy with respect to code safety, reliability, and
> robustness. It was put into C++ only as a political chit to keep the
> library vendors happy and to keep them from having to interwork

> directly - they made it the language's problem. From there the same bad


> design propagated into other languages for sake of familiarity. Good
> reliability and fault tolerance have to be customized to the
> application and platform where they are being used. I don't know how to
> do that in a generic example.

Agreed too. Real fault tolerance is hard. But exception handling gets you quite far, enough for most business apps.

> In your code below, how do you know that returning to the old Context
> doesn't leave databases locked, or a missile on a mistaken trajectory?

I don't. I only make sure that MY code (execute_with_context) will always do the right thing. Whether it's a good idea to catch the exception and continue is someone else's decision entirely (caller of execute_with_context). They might know a thing or two about the exception they're catching that I don't, or about the apps architecture.

Furthermore, I know that correct code always closes its DB connections. Sometimes I might also know about other side effects, e.g. when I'm using transactions - the missile might be launched but not committed.

> Error recovery is hard and doesn't come down to casually putting in a
> few catches and throws here and there. In a high-availability telecom
> system, such measures amount to 50% to 80% of the code mass.

Sure, errors in return values, checking every single one. But the rest of the world needs to get stuff done too, and experience tells us that we often get good enough recoverability using just a few lines of exception handling code. Your typical C# code uses few throws and catches, but lots of usings (try-with-ressources) and some try/finally blocks. This works quite OK and there's not really an alternative to it in many situations.

So frameworks need to handle this.


>
>
>
> On Aug 31, 2011, at 9:24 , Wenig, Stefan wrote:
>
> > Technical nitpicking: isn't execute_in_context missing some exception
> handling?
> > I'm not fluent in Ruby, in C# I'd write
> >
> > [ThreadStatic] public static Context {get; private set;}
> >
> > ExecuteInContext (Action act) {
> > oldContext = Context;
> > Context = this;
> > try {
> > a();
> > } finally {
> > Context = oldContext;
> > }
> > }
>

Serge Beaumont

unread,
Aug 31, 2011, 4:48:47 AM8/31/11
to object-co...@googlegroups.com

On 31 aug. 2011, at 10:00, Wenig, Stefan wrote:

> - the missile might be launched but not committed.

Heh, that one put a grin on my face.

"Well, yes sir, we… ahhhm… kind of launched the missile… What? The others have launched their nukes at us? But… but… we didn't commit the missile yet, so that doesn't count, right? …Sir?"

Thanks for making my morning :-)

Wenig, Stefan

unread,
Aug 31, 2011, 5:01:47 AM8/31/11
to object-co...@googlegroups.com
> From: Serge Beaumont
> Sent: Wednesday, August 31, 2011 10:49 AM

>
> On 31 aug. 2011, at 10:00, Wenig, Stefan wrote:
>
> > - the missile might be launched but not committed.
>
> Heh, that one put a grin on my face.
>
> "Well, yes sir, we... ahhhm... kind of launched the missile... What? The
> others have launched their nukes at us? But... but... we didn't commit the
> missile yet, so that doesn't count, right? ...Sir?"

Transactional life would be much easier in many ways. Can't stop global warming anymore? Let's pick a safepoint and start over!

Stephan Herrmann

unread,
Sep 1, 2011, 12:57:30 PM9/1/11
to object-composition
Hi again,

sorry for the delay, other tasks kept me busy, but now I have an OT/J
version of Dijkstra's algorithm which should look pretty similar to
both the Ruby version and the Wikipedia description.

The source code is available as an Eclipse project from
http://download.eclipse.org/objectteams/examples/dijkstra-otj.zip
Of course the sources are best viewed using the IDE. I'll also paste a
copy below, which btw. leverages the diamond operator introduced in
Java 7 :)

Unfortunately, bad timing didn't allow us to release the JDT 3.7.0
with support for Java 7 (as you may already know) so for OT/J with
Java 7 features you may want to wait for Eclipse 3.7.1 (Sept 23) - or
asked me for directions to install a release candidate.



Anyway, so what differences do I see?

I didn't see advantage in recursive calls so my main algorithm is a
loop like in Wikipedia. Let me know if you would like to see a
recursive variant for better comparison.

In my variant the context CalculateShortestPath has fewer fields,
since I try to put variables into the closest possible scope. This
includes two role fields 'tentativeDistance' and 'previous'. As Cope
indicated these can be transformed to associative arrays at an outer
scope - both forms are semantically equivalent, but IMO role fields
help to keep things local -> better local comprehensibility.

I merged the behavior of role Neighbor into DistanceLabeledGraphNode
in order to keep the behavior close to the field 'tentativeDistance'.

Similarly, I merged methods for reporting the result into the context
CalculateShortestPath, which reduces the need to move around lots of
internal state. Again, let me know if you consider this design choice
(i.e., separating both contexts) essential.

I placed role Intersection nested inside the Map role, because a node
can only answer its neighbors when seen in the context of a graph with
a Map role. This makes Map a (nested) team, which has API to answer
the neighbors of a contained node. Role Intersection can directly
access methods of its direct context (Map) with no need for any
explicit references.

So basically, these differences are just my deliberations, choices
that I like better, but staying even closer to the design of the Ruby
example should be easily possible.



Some things just look different but are basically the same:

The rebind and "extend" business in Ruby should pretty closely relate
to "as" signatures in OT/J. In Ruby "extend" happens inside method
bodies, whereas OT/J puts this into the interface as to separate
"inside" (role types) and "outside" (data types).

Java doesn't have closures, a long lasting pity. Please excuse the
more imperative style of some loops.



So I think both solutions look fairly similar, which leaves us with
one big difference: Ruby has fewer declarations whereas OT/J has
static type checking. I'd like to add that I favor static typing not
only for the sake of safety but also as the foundation for "smarter"
tooling: all navigation, code completion, quick fixes etc. provided by
the IDE heavily rely on that type information.


have fun,
Stephan

PS: and now for the code:

Preliminaries:
----------------------------------------------------------------------------
package common;

public class Numbers {
public static int INFINITY = Integer.MAX_VALUE;
public static int add(int a, int b) {
if (a == INFINITY || b == INFINITY) return INFINITY;
return a+b;
}
}
----------------------------------------------------------------------------
package data;

public class Node {
private String name;

public Node(String name) {
this.name = name;
}

public String getName() {
return this.name;
}
}
----------------------------------------------------------------------------
package data;

public abstract class Graph {

protected Node[] nodes;
protected Node root;
protected Node destination;

public int getNumNodes() { return nodes.length; }
public Node getNodeAt(int i) { return nodes[i]; }

public Node rootNode() { return this.root; }
public Node getDestination() { return this.destination; }

public abstract Node eastNeighborOf(Node node);
public abstract Node southNeighborOf(Node node);

public abstract int getDistance(Node from, Node to);
}
----------------------------------------------------------------------------
Now for the algorithm:
----------------------------------------------------------------------------
package algorithm;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import common.Numbers;

import base data.Graph;
import base data.Node;

/**
* Role-based implementation of <a href="http://en.wikipedia.org/wiki/
Dijkstra's_algorithm">Dijkstra's algorithm</a>
* using OT/J.
*/
public team class CalculateShortestPath {

private TraversedGraph geometries;
private Set<DistanceLabeledGraphNode> unvisited;
private DistanceLabeledGraphNode destination;

Map map;

public CalculateShortestPath(Node as DistanceLabeledGraphNode
originNode,
Node as DistanceLabeledGraphNode
targetNode,
Graph as TraversedGraph geometries,
Graph as Map map)
{
// setup all nodes from geometries as unvisited:
this.unvisited = new HashSet<>();
for(int i=0; i<geometries.getNumNodes(); i++)
this.unvisited.add(geometries.getNodeAt(i));

// use this to look up neighborship:
this.geometries = geometries;

// use the map to lookup distances:
this.map = map;

// setup the origin node:
originNode.tentativeDistance = 0;

// that's where we want to travel to:
this.destination = targetNode;
}

/**
* Role DistanceLabeledGraphNode attaches a tentative distance
from origin to a node
*/
protected class DistanceLabeledGraphNode playedBy Node {
public int tentativeDistance;
public DistanceLabeledGraphNode previous;

String getName() -> String getName();

public DistanceLabeledGraphNode(Node node) {
this.tentativeDistance = Integer.MAX_VALUE; // initially
we assume the worst.
}
/**
* if newDistance is better than the current tentativeDistance
use it from now on.
* @return true if the node has been relabled by this
operation.
*/
public boolean relableAs(int newDistance) {
if (newDistance < this.tentativeDistance) {
this.tentativeDistance = newDistance;
return true;
}
return false;
}
}

/**
* Role TraversedGraph provides all contained nodes as {@link
DistanceLabeledGraphNode}s.
*/
protected team class TraversedGraph playedBy Graph {
int getNumNodes() -> int getNumNodes();
DistanceLabeledGraphNode getNodeAt(int i) -> Node
getNodeAt(int i);
DistanceLabeledGraphNode eastNeighborOf(Intersection node) ->
Node eastNeighborOf(Node node);
DistanceLabeledGraphNode southNeighborOf(Intersection node) ->
Node southNeighborOf(Node node);

/**
* Inside a {@link TraversedGraph} we can view each node as an
intersection
* which can answer all its (unvisited) neighbors.
*/
protected class Intersection playedBy Node {
public List<DistanceLabeledGraphNode> unvisitedNeighbors()
{
List<DistanceLabeledGraphNode> retVal = new
ArrayList<>();
DistanceLabeledGraphNode southNeighbor =
southNeighborOf(this);
if (southNeighbor != null &&
unvisited.contains(southNeighbor))
retVal.add(southNeighbor);
DistanceLabeledGraphNode eastNeighbor =
eastNeighborOf(this);
if (eastNeighbor != null &&
unvisited.contains(eastNeighbor))
retVal.add(eastNeighbor);
return retVal;
}
}
/** Ask this graph for all unvisited neighbors of the given
intersection. */
public List<DistanceLabeledGraphNode>
getUnvisitedNeighbors(data.Node as Intersection intersection) {
return intersection.unvisitedNeighbors();
}
}

/**
* Role Map looks up the distance between to nodes.
* Fortunately the Graph already provides this functionality,
otherwise we would need to do more here.
*/
protected class Map playedBy Graph {

int getDistance(DistanceLabeledGraphNode from,
DistanceLabeledGraphNode to) -> int getDistance(Node from, Node to);

}

/**
* Execute Dijkstra's algorithm and return the node that serves as
a handle to the computed path.
*/
public data.Node execute() {
List<DistanceLabeledGraphNode> q = new
ArrayList<>(this.unvisited);
// The main loop
while (!q.isEmpty()) {
// u := vertex in Q with smallest dist[]
int minDist = Numbers.INFINITY;
DistanceLabeledGraphNode u = null;
for(DistanceLabeledGraphNode candidate : q)
if (candidate.tentativeDistance < minDist) {
minDist = candidate.tentativeDistance;
u = candidate;
}

if (minDist == Numbers.INFINITY)
break;

// remove u from Q
this.unvisited.remove(u);
q.remove(u);

for(DistanceLabeledGraphNode v :
this.geometries.getUnvisitedNeighbors(u)) {
int alt = Numbers.add(u.tentativeDistance,
this.map.getDistance(u, v));
// Relax (u,v,a)?
if (v.relableAs(alt)) {
v.previous = u;
// Reorder v in the Queue
q.remove(v);
q.add(0, v);
}
}
}
return this.destination;
}

/** print the nodes back-to-front starting with 'node' (like in
the Ruby example). */
public void print(Node as DistanceLabeledGraphNode node, boolean
verbose) {
DistanceLabeledGraphNode previous = null;
while (node != null) {
if (previous != null) {
if (verbose)
System.out.print(" - "+this.map.getDistance(node,
previous)+" -> ");
else
System.out.print(" -> ");
}
System.out.print(node.getName());
previous = node;
node = node.previous;
}
System.out.println();
}

/** Answer the path of visited nodes origin-to-destination. */
public List<data.Node> getPath(Node as DistanceLabeledGraphNode
node) {
List<data.Node> path = new ArrayList<>();
while (node != null) {
path.add(0, node);
node = node.previous;
}
return path;
}

/** Answer the given node's distance from the origin. */
public int getDistanceFromOrigin(Node as DistanceLabeledGraphNode
node) {
return node.tentativeDistance;
}
}
----------------------------------------------------------------------------
Test data and test driver:
----------------------------------------------------------------------------
package data;

import java.util.HashMap;
import java.util.Map;

import common.Numbers;

public class ManhattanGeometry extends Graph {

// note: this is a sparse map, nested maps may potentially be null
Map<Node,Map<Node,Integer>> distances = new HashMap<>();

Map<Node,Node> nextDownTheStreetFrom = new HashMap<>();
Map<Node,Node> nextDownTheAvenueFrom = new HashMap<>();

protected void connectSouth(Node from, Node to, int dist) {
Map<Node,Integer> nodeMap = this.distances.get(from);
if (nodeMap == null)
this.distances.put(from, nodeMap = new HashMap<Node,
Integer>());
nodeMap.put(to, dist);
this.nextDownTheAvenueFrom.put(from, to);
}
protected void connectEast(Node from, Node to, int dist) {
Map<Node,Integer> nodeMap = this.distances.get(from);
if (nodeMap == null)
this.distances.put(from, nodeMap = new HashMap<Node,
Integer>());
nodeMap.put(to, dist);
this.nextDownTheStreetFrom.put(from, to);
}
@Override
public Node eastNeighborOf(Node node) {
return this.nextDownTheStreetFrom.get(node);
}

@Override
public Node southNeighborOf(Node node) {
return this.nextDownTheAvenueFrom.get(node);
}

@Override
public int getDistance(Node from, Node to) {
Map<Node,Integer> nodeMap = this.distances.get(from);
if (nodeMap == null) return Numbers.INFINITY;
Integer value = nodeMap.get(to);
if (value == null) return Numbers.INFINITY;
return value;
}
}
----------------------------------------------------------------------------
package data;

public class ManhattanGeometry1 extends ManhattanGeometry {

public ManhattanGeometry1() {
String[]names = {"a", "b", "c", "d", "e", "f", "g", "h", "i"};
this.nodes = new Node[names.length];
for (int i=0;i<names.length;i++)
nodes[i] = new Node(names[i]);
/*
# Grid is of Manhattan form:
#
# a - 2 - b - 3 - c
# | | |
# 1 2 1
# | | |
# d - 1 - e - 1 - f
# | |
# 2 4
# | |
# g - 1 - h - 2 - i
#
*/
Node a = nodes[0], b = nodes[1], c = nodes[2],
d = nodes[3], e = nodes[4], f = nodes[5],
g = nodes[6], h = nodes[7], i = nodes[8];
this.root = a;
this.destination = i;
connectEast(a, b, 2);
connectEast(b, c, 3);
connectSouth(a, d, 1);
connectSouth(b, e, 2);
connectSouth(c, f, 1);
connectEast(d, e, 1);
connectEast(e, f, 1);
connectSouth(d, g, 2);
connectSouth(f, i, 4);
connectEast(g, h, 1);
connectEast(h, i, 2);
}
}
----------------------------------------------------------------------------
package data;

public class ManhattanGeometry2 extends ManhattanGeometry {

public ManhattanGeometry2() {
String[]names = {"a", "b", "c", "d", "e", "f", "g", "h", "i",
"j", "k"};
this.nodes = new Node[names.length];
for (int i=0;i<nodes.length;i++)
nodes[i] = new Node(names[i]);
/*
# Grid is of Manhattan form:
#
# a - 2 - b - 3 - c - 1 - j
# | | | |
# 1 2 1 |
# | | | |
# d - 1 - e - 1 - f 1
# | | |
# 2 4 |
# | | |
# g - 1 - h - 2 - i - 2 - k
*/
int n = 0;
Node a = nodes[n++], b = nodes[n++], c = nodes[n++], d =
nodes[n++], e = nodes[n++], f = nodes[n++],
g = nodes[n++], h = nodes[n++], i = nodes[n++], j =
nodes[n++], k = nodes[n++];
this.root = a;
this.destination = k;
connectEast(a, b, 2);
connectEast(b, c, 3);
connectEast(c, j, 1);

connectSouth(a, d, 1);
connectSouth(b, e, 2);
connectSouth(c, f, 1);
connectSouth(j, k, 1);

connectEast(d, e, 1);
connectEast(e, f, 1);

connectSouth(d, g, 2);
connectSouth(e, h, 4);

connectEast(g, h, 1);
connectEast(h, i, 2);
connectEast(i, k, 2);
}
}
----------------------------------------------------------------------------
import java.util.List;

import algorithm.CalculateShortestPath;
import data.Graph;
import data.ManhattanGeometry1;
import data.ManhattanGeometry2;
import data.Node;

public class Main {
public static void main(String[] args) {
doDijkstra(new ManhattanGeometry1(), false);
doDijkstra(new ManhattanGeometry2(), true);
}
static void doDijkstra(Graph geom, boolean verbose) {
CalculateShortestPath dijkstra = new
CalculateShortestPath(geom.rootNode(), geom.getDestination(), geom,
geom);
Node pathHandle = dijkstra.execute();
dijkstra.print(pathHandle, verbose);
// alternative way of using the result: request a forward-
sorted list of nodes:
printForward(dijkstra.getPath(pathHandle));
System.out.println("Distance is
"+dijkstra.getDistanceFromOrigin(pathHandle));
}
static void printForward(List<Node> path) {
for (Node node : path)
System.out.print("Node "+node.getName()+" ");
System.out.println();
}
}
----------------------------------------------------------------------------

James O. Coplien

unread,
Sep 1, 2011, 2:24:02 PM9/1/11
to object-co...@googlegroups.com
Please re-do it to keep the design in concert with the original. The original is not designed to be optimal in any sense, but to showcase and explore certain design questions. It's going to be hard to compare yours with the original given the changes. If you could revert to the original design, that would better serve our purpose. Thanks.

> --
> You received this message because you are subscribed to the Google Groups "object-composition" group.

> To post to this group, send email to object-co...@googlegroups.com.
> To unsubscribe from this group, send email to object-composit...@googlegroups.com.

Ant Kutschera

unread,
Sep 2, 2011, 3:56:58 AM9/2/11
to object-composition
I'm sticking as close to the original as I can, using my Behaviour
Injecjtor. I wanted to use simple POJO wrappers, but it wasn't
possible. You assign multiple roles to the same object (which is
find), and then because Ruby is dynamic, you can call whatever method
you want, from where ever. In statically typed languages, you need to
have a typed reference to the object. So, I have to choose which role
it is playing, and can only access methods from that role. If I want
to access methods from a different role, I have to somehow cast it in
to a different role. That could by why Stephan chose to merge the
Neighbour and DistanceLabeledGraphNode. Anyway, using POJO would
require me to build a composite object containing all the roles, and
the solution, while possible, is unsatisfactory. By using my
Behaviour Injector, I can leverage reflection and with very little
code, I can create a reference to a megapimped object, so that I can
call any method from anywhere, just like you do.

Your code, for example:

node;
node.extend Distance_labeled_graph_node
node.extend Neighbor

node.someMethodFromDistance_labeled_graph_node
node.someMethodFromNeighbor

Statically typed language, like Java:

Neighbor neighbor = assignRole(node, Neighbor);
Distance_labeled_graph_node dlgn = assignRole(node,
Distance_labeled_graph_node);

neighbor.someMethodFromDistance_labeled_graph_node(); //illegal,
and won't compile!

But, this has little to do with whether we use wrappers or not, and a
lot more to so with statically typed vs. dynamically typed languages.
Nonetheless, I shall continue, and post my solution shortly.

What I can say so far, the problems I've had to solve are not related
to hash codes and lookups in associative arrays, or with object
schizophrenia. That did surprise me a little, because while I didn't
think it would be a problem, I thought that such a complex example
would pose a few problems in this area.

My problems are related to having one object play several roles at one
time, and not having the correct reference type when I need it.

@Stephan: merging the roles implementations isn't satisfactory to me,
because the role implementations should be seperate, in order to match
the mental model. I think the only valid solution is to refer to an
object playing both roles, by using an interface which extends both
role interfaces. Think about the use case, and how the analyst thinks
of the different roles. If you just merge it all into one pot, you
end up with spaghetti, IMO.

Ant
> ...
>
> read more »

Ant Kutschera

unread,
Sep 2, 2011, 3:58:28 AM9/2/11
to object-composition
PS. James, is this a bug in your code?

class ManhattanGeometry1
def initialize
@nodes = Array.new
@distances = Hash.new

names = [ "a", "b", "c", "d", "a", "b", "g", "h", "i"]


Nodes have the same name (a, b)!

It must be a bug, because you override eql? and ==, and base them on
the name:

class Node
attr_reader :name
def initialize(n); @name = n end
def eql? (another_node)
# Nodes are == equal if they have the same name
return name == another_node.name
end
def == (another_node)
# Equality used in the Map algorithms is object
identity
super
end
end


Ant

James O. Coplien

unread,
Sep 2, 2011, 4:02:46 AM9/2/11
to object-co...@googlegroups.com

On Sep 2, 2011, at 9:56 , Ant Kutschera wrote:

> That could by why Stephan chose to merge the
> Neighbour and DistanceLabeledGraphNode.


Just could be...

James O. Coplien

unread,
Sep 2, 2011, 4:16:02 AM9/2/11
to object-co...@googlegroups.com
On Sep 2, 2011, at 9:56 , Ant Kutschera wrote:

> But, this has little to do with whether we use wrappers or not, and a
> lot more to so with statically typed vs. dynamically typed languages.
> Nonetheless, I shall continue, and post my solution shortly.

Aha. I knew this comment would come up.

There are two ways of approaching DCI. One is to look at its principles and to fit as many of them as possible into a current language, as unsuitable as it may be. The other is to look at how to design a language that meets DCI's objectives. There are several people at each end of this spectrum.

More interesting, I think, is the engineering space in the middle: to find a language that is suitable *enough* while still not compromising DCI principles. We'll see what Stefan comes up with in response to my request to be faithful to the original design, but my hunch is the same as yours with respect to his changes to the design. I'd be surprised if you can use that technology and be faithful to the DCI principles. Now, mind you, I'd love to be surprised, but I've explored this space enough that it will be a *really* big surprise...

I don't want to constrain DCI because someone's language doesn't accommodate it, and I don't want to trim DCI or compromise it for the sake of fitting the language du jour. We have to be honest about the caveats rather than skirt them. And we need to mitigate them when we can — by evolving the base language, by moving to a language that better supports the design goals of a given project, and so forth. I wouldn't choose Java at this point if DCI were important to me unless I was willing to invest in Qi4j (a pretty cheap investment :-) ). I believe that those who have a gun to their head to use Java, use DCI at their peril, and at the ultimate peril of those holding the gun.

Again, I'd love to be surprised...


> @Stephan: merging the roles implementations isn't satisfactory to me,
> because the role implementations should be seperate, in order to match
> the mental model.

Agreed. And even if it doesn't match your particular mental model in this case, the general principle at stake here is that there should be no limitation about how many roles a given object can play, and there should be only reasonable limitations on how those roles can invoke each other (e.g., method name collision concerns).


> My problems are related to having one object play several roles at one
> time, and not having the correct reference type when I need it.

Your current post is one of the more intriguing mails I've seen on this list because I feel there's an insight here into a different twist on the implementation. But the above paragraph worries me. It also leaves me hungry because I can't understand in enough detail what you mean. Say more. Show code. You said you'd post your solution shortly and I'm eager to see it. Let's see if we can work this particular issue.

Should we move this to DCI evolution? Ant, it's your thread ...

Ant Kutschera

unread,
Sep 2, 2011, 4:16:32 AM9/2/11
to object-composition
Yes, but it doesn't have to be, as you will see when I post my
solution. The BehaviourInjector doesn't require you to merge the
roles, just the interface, and doing so can be done with almost no
extra code written. The reason you need such a mega pimped interface,
is because it's statically typed, rather than dynamically typed.

If you think creating an interface to both roles is illegal, then you
are declaring that statically typed languages cannot do DCI.

I can equally declare dynamically typed languages cannot do DCI,
because they cannot prevent surprises.

Consider:

person;
if(someValue == Random.getNextRandomNumber()){
person assign Devil;
}

//sometime later in the code
person.doSomethingDevilish(); //method from devil role

Now, will that method call fail or not? Hmm, I guess we have no idea,
until we run the code, and each time it runs, we might be surprised.

The above code in a statically typed language wouldn't cause problems,
because it guarantees that the object has the methods, because the
reference to the object is typed.

So, basically, statically typed languages cant do DCI, nor can
dynamically typed ones. Oh boy...

James O. Coplien

unread,
Sep 2, 2011, 4:19:47 AM9/2/11
to object-co...@googlegroups.com
Aha! So you found the first Easter Egg in my code.

No, it is not a bug. It is intentional. The corners are named after the names of the firms that have the main anchor stores on the corresponding corner. The "a" stands for MacDonald's.

Why did I do this? In can show up a bug in any implementation that confuses shallow equality, deep equality, and identity. DCI depends strongly on object identity and while it must support shallow and deep quality for the sake of the programmer, DCI itself doesn't depend on it. If someone has a "DCI" framework that depends on equality instead of identity, their implementation won't work. This data structure is there to explicitly elicit that bug.

There are more Easter Eggs. Beware. This code is not what it appears to be, and even if you get it to run and print results, check them carefully...

James O. Coplien

unread,
Sep 2, 2011, 4:25:03 AM9/2/11
to object-co...@googlegroups.com

On Sep 2, 2011, at 10:16 , Ant Kutschera wrote:

> Yes, but it doesn't have to be, as you will see when I post my
> solution.


Go for it.

James O. Coplien

unread,
Sep 2, 2011, 4:26:16 AM9/2/11
to object-co...@googlegroups.com

On Sep 2, 2011, at 10:16 , Ant Kutschera wrote:

> If you think creating an interface to both roles is illegal, then you
> are declaring that statically typed languages cannot do DCI.


Sorry for the confusion. I don't think it's illegal, and I have a perfectly good implementation of Dijkstra running in C++ as well, thank-you :-)

Ant Kutschera

unread,
Sep 2, 2011, 4:28:25 AM9/2/11
to object-composition
The reason I havent posted my code yet, is that it is printing 0 as
the distance ;-)

I have been thinking about identity and equality, and how I use them
in day to day life. Since we send object from clients to servers and
back again, we never ever use the Java == to test identity. We ALWAYS
rely on the equals method and hashCode methods.

I personally, don't see that == is so important, because I never ever
ever use it.

rune funch

unread,
Sep 2, 2011, 4:38:58 AM9/2/11
to object-co...@googlegroups.com
> If you think creating an interface to both roles is illegal, then you
> are declaring that statically typed languages cannot do DCI.
That's to strict. In F#, which is stricter typed than Java and C# you
can do if without any any type implementing any specific interfaces.

Ant Kutschera

unread,
Sep 2, 2011, 5:06:17 AM9/2/11
to object-composition
In C++ do you use a mega-pimped interface containing methods of all
the roles?

Ant Kutschera

unread,
Sep 2, 2011, 5:07:07 AM9/2/11
to object-composition
How?

rune funch

unread,
Sep 2, 2011, 5:09:35 AM9/2/11
to object-co...@googlegroups.com
You can restrict the arguments to a function based on there
capabilities. It won't be feasible in all scenarios but interfaces are
not strictly needed

Mvh
Rune

Ant Kutschera

unread,
Sep 2, 2011, 5:14:18 AM9/2/11
to object-composition
cool - can you show some code?

On Sep 2, 11:09 am, rune funch <funchsolt...@gmail.com> wrote:
> You can restrict the arguments to a function based on there
> capabilities. It won't be feasible in all scenarios but interfaces are
> not strictly needed
>
> Mvh
> Rune
>

James O. Coplien

unread,
Sep 2, 2011, 5:49:19 AM9/2/11
to object-co...@googlegroups.com
On Sep 2, 2011, at 10:28 , Ant Kutschera wrote:

> The reason I havent posted my code yet, is that it is printing 0 as
> the distance ;-)
>
> I have been thinking about identity and equality, and how I use them
> in day to day life. Since we send object from clients to servers and
> back again, we never ever use the Java == to test identity. We ALWAYS
> rely on the equals method and hashCode methods.


Right — the SOA approach, more or less, right? I am with you and think that I understand your rationalization.

There is a rich, rich set of questions that arise here. I am guessing that you really don't send the objects in your system. Objects have intelligence. Most SOA implementations (and all CORBA-based implementations) send only the object state. They don't send the data ("data" in the DCI is a representation that includes a minimal interpretation of the functionality: therefore, 32 bits could be either an integer or a floating point number, but the method contextualization makes it data). They don't send the role part — the .text of the methods.

My model, therefore, is that there is an object on each side of such a transaction (to pick a suitably selected word). It isn't that you send objects from clients to servers and back, but you provide the illusion by replicating a *copy* of the object on the receiving end. Think Pascal call-by-value. Students are usually taught call-by-value before call-by-reference (or, heaven forbid, call-by-name).

We explored these questions in depth when we built the first object broker back at Bell Labs in my department, circa 1982 or 1983. What a pain.

One reason this is confusing is that this is also the "ordinary" way that CS students are taught that compilers pass information between procedures: call-by-value, by putting a copy on the stack frame. CS students come out of their education with this mental model that a thing is equivalent to its name. Copying an object onto the stack frame, or onto another computer, results in a new object with a new name. The new name in the SOA case is qualified by the scope of the processor it sits on. The new name in Pascal is on the activation record of the receiving function.

In either case, if you ask if both objects are the same one, it takes an interesting perspective on the world to answer: "yes." They have separate identity. If you ask if they are equal, the answer is: it depends. Shallowly equal or deeply equal? That is, do they have the same graphs of references to other objects or not?

The goal in CORBA (or other RPC mechanisms) or SOA is to sometimes provide the *illusion* that it is the same object. Programmers are not under this illusion; they carefully have to make sure that a copy at one end is replicated at the other. This situation exists in other, more static settings such as telecom. The concept of a "phone call object" straddles two switching systems in a city-to-city call, and one must maintain the *illusion* that there is one call object. That is ridiculous, of course, if the call object has an identity (unless you put some really bizarre scenarios in place), so we have patterns like HOPP whose job it is to keep the endpoints in synchronization. That is all above the programmer's object model. It is on this side of the programmer's abstraction boundary.

Telecom has even more interesting problems here because of the need to synchronize the intelligence (the .text code) on all the machines that can touch that object's data. You can't shut down The Bell System to be able simultaneously to update all these machines, so there is some pretty deep magic for making this work — on the underside of the abstraction boundary.

The reason this becomes an interesting question is that we must consider where it lives in the end user mental model: what are their abstraction boundaries, and on what side of the abstraction boundary does this functionality lie? My experience is that it is worthwhile educating the business about the risks of such replication, because it takes the investment of transaction semantics (or something similar) to meet the integrity requirements of distributed financial systems. If you go for a large budget to "just send an object across the wires" you'll get funny looks.

And that, in turn, has repercussions in the applicability of DCI in those contexts. I don't see it fitting there. That isn't a slam. Distributed systems are real, and the problem of distributing true objects is hard. Many have tried and failed: Ontos and its kin back in the 1980s all tried and tended to fail pretty badly because it was so hard to make them work. That is not the problem that DCI sets out to solve.

That said, you can still use DCI in concert with patterns like Proxy in the same way we always have. DCI doesn't hide the fact that there are multiple objects any more or any less than Proxy does. It is crucial, as an architect, to separate the Proxy semantics from the DCI semantics. I think that part of the problem in some of our discussions here is that people have hopes of using DCI to do the hiding that Proxy does. Proxy is a "wink" that keeps it visible that there is a man behind the curtain who is talking to the Great and Powerful Oz, the latter being on this side of the abstraction boundary. In DCI, there is no curtain, and instead of one actor handing off to another, it's just one Wizard. No illusions.

The question in the end depends on where you draw your abstraction boundaries. You will draw them with different kinds of pens, and knives, and walls, of which DCI is one. And your mileage may vary. I think it's pretty clear which ones work where, but it's always a matter of fitting a new paradigm onto all worldviews no matter where you come from. DCI is different from just about everything else out there. The CORBA / SOA paradigm is just one of them and, yes, it shapes how you think about things — including DCI.

Good insight, Ant.

James O. Coplien

unread,
Sep 2, 2011, 6:01:10 AM9/2/11
to object-co...@googlegroups.com

On Sep 2, 2011, at 11:06 , Ant Kutschera wrote:

> In C++ do you use a mega-pimped interface containing methods of all
> the roles?


There are too many imprecise terms there for me to be able to answer...

The object, at run time, supports all the methods for all the roles it will play. That is a casualty of C++ technology, and it's extensively explored in the Lean Architecture book. It's kind of a pretense, though, since run-time C++ objects are just blobs of passive bits — unless you use virtual functions, in which case there is actually some run-time type information. However, I don't use virtual functions (certainly not in role methods!), because that kind of goes against one or two of the top three DCI principles related to code readability. So the object's ability to respond to the messages is in the mind of the programmer and must be arranged by good design. Of course, the compile-time type checking system helps a LOT.

(I learned a software engineering lesson while working on the Dijkstra example. The Ruby implementation was immediately runnable but it stumbled dozens of times before running. The nice thing about Ruby is that it was ALWAYS clear what was wrong and the fix was usually easy, but in any case the consequences were always clear. It took much, much longer before the C++ example ran the first time, and the compile-time typing took me into cycles: started with 120 bugs; get it down to about 10; then, one particular fix allowed the compiler to effectively go on to another pass and it went up to 120 again. I went through five or six such cycles. Maybe that's a way of figuring out how many compiler passes there are.)

But to answer from a DCI perspective:

- The roles are each self-contained, without reference to any other role,
except for use of other roles' methods

- The Node class is set up at compile time with all possible roles
pre-injected; however, there are only templates in the source, and no
classes. So what for a Smalltalk programmer is a class / object
dichotomy, for a C++ programmer is a template / class dichotomy;
thereafter, the objects are more or less just blobs of bits. The good
news is having the ability to reason more formally about the what-
the-system-is structure at compile time.

- In any given context of use of a Node object, the interfaces only
of the roles at hand are accessible and visible.

The code works but needs serious cleaning up — another casualty of the way that C++ language rules force certain modularization of templates. I'm going to radically re-factor it (or at least try) to make it more readable to a novice and will ultimately post it here.

rune funch

unread,
Sep 3, 2011, 5:25:46 AM9/3/11
to object-co...@googlegroups.com


2011/9/2 Ant Kutschera <ant.ku...@gmail.com>

cool - can you show some code?

let inline getName arg =
  ( ^a : (member Name : string) arg)
The above lets you call getName with anything that has a instance property called 'Name' or if you wish to restrict on an operator you can do
let inline implicit arg =
  ( ^a : (static member op_Implicit : ^b -> ^a) arg)
Which then lets you call with any argument that supports an implicit conversion to the return type of the function. The return type is inferred by the compiler
you can restrict multiple arguments in the same manner. Both examples above are taken from
http://codebetter.com/matthewpodwysocki/2009/06/11/f-duck-typing-and-structural-typing/
/Rune

Stephan Herrmann

unread,
Sep 3, 2011, 7:21:08 AM9/3/11
to object-composition
Before I post my new version of the OT/J example a few comments to
other posts that have piled up in the meantime:

@Ant:
I very much agree with your observations about fully unprotected Ruby
code vs. statically type-checked code. I, too, found some interesting
things in the Ruby code that should be addressed.

@Ant:
> @Stephan: merging the roles implementations isn't satisfactory to me,
> because the role implementations should be seperate, in order to match
> the mental model

If I knew the mental model of the person who wrote the code I would
with pleasure follow that. Having only the code I had to reverse
engineer the mental model and just didn't succeed from the code of
Distance_labeled_graph_node and Neighbor. If you look at the code the
nature of the former role is solely to introduce a new property and
the nature of the latter role is solely to operate on that property.
So clearly a Neighbor cannot exist if that thing isn't also a
Distance_labeled_graph_node. Of course these two can be separated, but
then we also need to connect them again. In Ruby this connection is
implicit/invisible, but in statically typed languages we have to work
harder to demonstrate *why* actually the code doesn't blow up.

IMO, introducing the megapimped interface doesn't help to separate
roles, either.

@Cope:
> Agreed. And even if it doesn't match your particular mental model in
> this case, the general principle at stake here is that there should be
> no limitation about how many roles a given object can play, and there
> should be only reasonable limitations on how those roles can invoke
> each other (e.g., method name collision concerns).

I didn't choose to merge roles because multiple roles would in any way
be impossible, they are possible also in OT/J. Having a static type
system just forces me to be honest about what a thing is. One example:
in the Ruby example, what kind of thing is "current"? In one line I
see
unvisited_neighbors = current.unvisited_neighbors
so it must be a CurrentIntersection, three lines down I see
... current.tentative_distance ...
so it must be a Distance_labeled_graph_node. So at this point the
separation between these roles is broken, and re-building the same
design in a statically typed language has to make the separation
explicit. Code will follow in a minute.


cheers,
Stephan

Stephan Herrmann

unread,
Sep 3, 2011, 8:01:16 AM9/3/11
to object-composition
Alright, a new version has been uploaded to
http://download.eclipse.org/objectteams/examples/dijkstra-otj-deoptimized.zip

This version keeps roles separate and generally looks more like the
Ruby original (trading this against direct structural match with the
algorithm as specified in wikipedia - but that's probably not the
point here).

Please don't consider this as a submission to a beauty contest, I
still like the previous version better. But for plain comparison what
is possible in what language maybe this helps.

Some changes were easy as expected, others required a second (and
third) look.

0. observation: I can do without megapimped interfaces, so I haven't
tried that road.

1. observation: the field 'current' had to be split into 2:
Intersection currentIntersection;
DistanceLabeledGraphNode current;
the former is used for finding neighbors, the latter for computing
distances.

2. observation: before I can asked 'Neighbor v' for its tentative
distance I have to switch roles like this
DistanceLabeledGraphNode vWithDistance = asDLGN(v);
where asDLGN() is defined like this
protected DistanceLabeledGraphNode asDLGN (Node as
DistanceLabeledGraphNode node) {
return node;
}

3. observation: some other role switches actually blend nicely with
the existing design, consider e.g. role TraversedGraph:
protected team class TraversedGraph playedBy Graph {
//...
Neighbor eastNeighborOf(Intersection node) -> Node
eastNeighborOf(Node node);
Neighbor southNeighborOf(Intersection node) -> Node
southNeighborOf(Node node);
}
Given two existing methods Node->Node in the role player Graph, these
signatures promise that given an Intersection we can ask for the
corresponding Neighbors. This support is built into OT/J and shows
that we not only have views of objects (Node) but also of relations,
where we lift the Node->Node relation to an Intersection->Neighbor
relation. That's our heritage from the Lieberherr group and their
traversal strategies :)

4. observation: given that 'path' is a data structure that will be
passed to clients (as the result of the algorithm) it cannot easily
use roles (clients outside the team should not access roles of the
team). Therefor the element type of 'path' is declared as data.Node.
Necessary conversions from roles to the pure data object are implicit
(the language guarantees that it works safely). The same may apply
also to 'pathTo', but I wasn't sure if this should be considered as
exposable.

5. observation: recursive invocation is generally no problem. Doing it
exactly the same way as in the Ruby example opens a whole new can of
worms, which I preferred to keep closed at this point. One discussion
at a time :)

Suggestions for discussion: maybe the separation between Neighbor and
Distance_labeled_graph_node is more meaningful at the level of fields
in the context, rather than at the level of class definitions? Having
fields like southNeighbor and currentDistanceNode makes sense to me,
but perhaps they just have the same type?

cheers,
Stephan

Ant Kutschera

unread,
Sep 4, 2011, 10:35:25 AM9/4/11
to object-composition
On Sep 3, 1:21 pm, Stephan Herrmann <stephan.herrm...@onlinehome.de>
wrote:
> @Ant:
> If I knew the mental model of the person who wrote the code I would
> with pleasure follow that.

Yes, all too often on this forum, we neglect the mental model, and
jump in at the code level. This is one of the things I was making a
point about in the thread "identifying roles". If you want the code
to read like the use case, you need to know what the use case is.
Although, some might say that reading the code, is reading the use
case, because in DCI the two are so similar. I wouldn't mind seeing
the use case though.

> IMO, introducing the megapimped interface doesn't help to separate
> roles, either.

If any role implementation simply implements the role's interface
(Java terminology), then creating the megapimped interface is as
simple as:

interface MegaPimped extends Neighbor, DistanceLabelledGraphNode,
CurrentIntersection {}

To me, as business programmer, these things are a little confusing,
and I struggle to come up with a useful name for this megapimped
object.

If I think about my "Books sold at a Till" example, I really would
like to keep the implementations from the sales log, sales order
database and printer separated. But I could imagine combining them
into an interface called "Basket" within the sales context, where the
Basket (data) plays these roles. I must say though, I haven't yet
needed to use such a complex confused interface, because my use case
is simple enough that I ask the sales log to do the things it is
responsible for, and the printer to do the things it is responsible
for, and there is need to refer to a merged interface. I haven't yet
decided if using a merged interface is a bad thing or not. It seems
to make senes in this example of Djikstra's Algorigthm, and it seems
unnecessary for my Sales Till example. I guess the more examples we
program, the more our collective experience will tell us which is more
suitable.

> One example:
> in the Ruby example, what kind of thing is "current"? In one line I
> see
>   unvisited_neighbors = current.unvisited_neighbors
> so it must be a CurrentIntersection, three lines down I see
>   ... current.tentative_distance ...
> so it must be a Distance_labeled_graph_node. So at this point the
> separation between these roles is broken, and re-building the same
> design in a statically typed language has to make the separation
> explicit. Code will follow in a minute.

I wonder if one could go as far as saying, that by confusing the
roles, one has designed the algorithm badly.

If in my Till example, the use case was written so that the analyst
thought of asking the sales log to prepare the output for the printer,
but then gave that output to the printer to print (printer and sales
log are played by the basket object), I would jump up and say the use
case needed refactoring, and the analysts mental model was poor and
unclear. There is no separation of concerns in their model.

But, like I said earlier, I don't have a problem with pimping a node
with several role, and then asking it to do a mix of things. What I
am against, is that a dynamic language cannot help me decide what that
object can do, because not being statically typed, there is no way for
the tooling to aid me with auto-complete.

Ant

Ant Kutschera

unread,
Sep 4, 2011, 10:50:51 AM9/4/11
to object-composition
On Sep 3, 2:01 pm, Stephan Herrmann <stephan.herrm...@onlinehome.de>
wrote:
> 1. observation: the field 'current' had to be split into 2:
>     Intersection currentIntersection;
>     DistanceLabeledGraphNode current;
> the former is used for finding neighbors, the latter for computing
> distances.

I have a problem here, because you now have two objects. It's not
just object schizophrenia, its really two objects. Does the user have
two objects in their mental model, or a single node which needs
several types of behaviour - ok, it's impossible to know without an
explicitly written use case :-)

> 2. observation: before I can asked 'Neighbor v' for its tentative
> distance I have to switch roles like this

My second attempt, which I haven't posted yet, does role switching,
but I am not that happy. That is why I am playing with a merged
interface. Maybe that will be unsatisfactory too, lets see. I don't
like switching roles, because its extra boilerplate code, which you
read, but which isn't found in the use case, and so just confuses
everything.

> 3. observation: some other role switches actually blend nicely with
> the existing design, consider e.g. role TraversedGraph:

Can we all rename "Map" into "CartographyMap"? That way, we get no
name clashes with the Java "Map", which is an associative array.
I pushed the two common parts of the ManhattanMap into a class called
Geometry - that was done, so I could pass both objects to the
contexts. I was a little surprised James didn't do that already. Was
there a reason?

> 4. observation: given that 'path' is a data structure that will be
> passed to clients (as the result of the algorithm) it cannot easily
> use roles (clients outside the team should not access roles of the
> team). Therefor the element type of 'path' is declared as data.Node.
> Necessary conversions from roles to the pure data object are implicit
> (the language guarantees that it works safely). The same may apply
> also to 'pathTo', but I wasn't sure if this should be considered as
> exposable.

Good observation.

Ant Kutschera

unread,
Sep 4, 2011, 10:56:10 AM9/4/11
to object-composition
On Sep 3, 11:25 am, rune funch <funchsolt...@gmail.com> wrote:
> 2011/9/2 Ant Kutschera <ant.kutsch...@gmail.com>
>
> > cool - can you show some code?
>
> let inline getName arg =
>   ( ^a : (member Name : string) arg)
>
> The above lets you call getName with anything that has a instance
> property called 'Name'

wow - kinda wierd, but kinda cool.

I guess this massively helps reduce the inheritance hierarchy. On the
other hand, I bet in real world examples, where your method requires
20 fields, some of which are in child objects, it might be easier to
use base types?

Ant Kutschera

unread,
Sep 4, 2011, 11:00:45 AM9/4/11
to object-composition
On Sep 4, 4:35 pm, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> ...for, and there is need to refer to a merged interface.

doh - i didnt review before sending. should read:

"for, and there is NO need to refer to a merged interface. "

Sorry.

Ant Kutschera

unread,
Sep 4, 2011, 11:35:05 AM9/4/11
to object-composition
On Aug 30, 2:46 pm, "James O. Coplien" <jcopl...@gmail.com> wrote:
>         module Neighbor
>                 include ContextAccessor
>
>                 def relable_node_as(x)
>                         if x < self.tentative_distance; self.set_tentative_distance_to(x); return true
>                         else return false end
>                 end
>         end

Hi James,

Here, the role Neighbor is acessing the tentative_distance field of a
different role.

Is that an easter egg?

How do you think that affects separation of concerns? What does DCI
have to say about separation of concerns?

Thanks for your input.

Ant

Ant Kutschera

unread,
Sep 4, 2011, 4:44:35 PM9/4/11
to object-composition
Hi James,

Quick question: your rebind method gets called everytime the context
is restarted upon recursion. That means, objects which are already
playing the role of say Neighbor are cast into that role more than
once.

How would you expect a DCI certified language to deal with that?

I only see it as a problem, if the role contains state, like your
Distance_Labelled_Graph_Node.

Thanks,
Ant

Ant Kutschera

unread,
Sep 4, 2011, 6:04:34 PM9/4/11
to object-composition
OK, I have a working examle with wrappers and mega interfaces, using
my behaviour injector to leverage reflection. I shall post shortly.

What I can say is, that in order to make this example with wrappers, I
have to adhere to a certain number of rules, so that there are no
surprises. The main one is:

- never ever use the "==" operator.

I will post in the next days.

Ant

rune funch

unread,
Sep 5, 2011, 3:44:46 AM9/5/11
to object-co...@googlegroups.com


2011/9/4 Ant Kutschera <ant.ku...@gmail.com>
I'll never have 20 fields needed in a real world example. I wouldn't even leave my "play ground code" that untidy :)
But more to the point I can come up with a gazillion of examples where the above doesn't seem to be a good fit. My original point was simply that even in strongly typed languages you can have other mechanisms than interfaces to ensure certain aspects of the objects used.

Stephan Herrmann

unread,
Sep 6, 2011, 8:46:29 AM9/6/11
to object-composition
Ant,

From some of your posts it seems that the two of us have slightly
different opinions (sic) about what deserves separation and what
doesn't. Therefore, I'll try to bring my POV down to more general
terms. The first term is a very traditional term: "abstraction", the
second one will be more suitable for a paradigm shift.

In my understanding "abstraction" is useful in programming as a
powerful means of simplification: you define a concept specifying as
many details as you like but then you give a single name to the
concept. This name allows you to refer to the concept and leverage its
strength without repeating the details. Primary examples of
abstractions in this sense are functions, types and maybe classes. In
statically typed languages I use types as what connects a variable to
a specification, where the specifications says what properties values
of that variable should have.

From what you wrote I get the impression you are very keen on
separation at the side of *specifying* a concept, but then you don't
seem to *use* the name you gave to that concept. More precisely, if
you separate roles Neighbor and DistanceLabeledGraphNode but for your
variables you don't want to use these types but only the type
Neighbor&&DistanceLabeledGraphNode&&Intersection then I don't see the
value of being so strict about the separation. You don't seem to use
it. Where in the program would the name "Neighbor" occur (and mean
exactly "Neighbor", not also any of the other roles?). Of course we
can connect abstractions in logical ways, like one abstraction implies
the other, like, if I say "rectangle" I always want you to also think
"quadrangle" etc. Implications can be modeled using subtyping, right?

OK, in the field of "abstraction" much has been said and done and I
don't claim that OT/J has anything new to offer in this dimension.

I'm much more interested in supporting "views" as a primary tool for
thinking and programming. This is what roles can bring to the table.
Abstraction only connects one definition of a name with the many uses
of that name. Views imply that the same thing can be regarded from
different angles. Working with views emphasizes that each name you use
may not describe the thing in all its details, not even all its
relevant details. For me using a role name means two things: right now
I'm only interested in the properties ascribed to the role, but I'm
always aware that the same thing can also play different roles so
switching the perspective will surface other properties.

You seem to say that role switching is a mentally heavy operation that
we should avoid in programming. I disagree, I believe that role
switching is at the core of creative human thinking and performed so
frequently in our daily thinking that we don't even notice it most of
the time. (Here I don't speak of myself switching between different
roles, but of interpreting the same object/person using different
perspectives/roles). I think we could very well say to the customer
who wants to buy the Dijkstra implementation: up-to this point we
considered this node as an intersection (implying it has some
neighbors), but now lets focus on the fact that we can also regard it
as a thing that has a distance from the origin so when switching to
this view lets ask it about it's current estimate of that distance.
Surely that is quite verbose and sounds heavy weight, but this is
spelled out what in OT/J "Node as DistanceLabeledGraphNode" precisely
states.
(As an aside: role switching is more common when moving from one
context to another, and then in OT/J you will only notice the switch
of contexts, roles will just follow. But nothing stops you from having
different roles of the same thing even within the same context.)

Putting both together, abstractions and views, I'm free in my design:
I can create as many named abstractions as I want to use in my
program. Some names will be connected by logical implication, fine I
can use subtyping. Some names are alternative descriptions of the same
thing with no direct logical relation, fine I can use role switching
to move from one perspective to the other. This greatly supports very
precise descriptions and reasoning. Technically, I can declare
variables with precise types bringing exactly those properties into
focus that I need for the task at hand. Even code completion in the
IDE is focused: proposing methods only of the role that's in focus. If
that role is not what I meant I need to take one more hop: from the
current role to another role to that role's method. Goal.

I believe this is a mildly formal description of a mental model that
all of us already apply all the time.

And all of this is directly supported by OT/J as shown in the
example :)

cheers,
Stephan


Stephan Herrmann

unread,
Sep 6, 2011, 9:01:41 AM9/6/11
to object-composition
On Sep 4, 4:50 pm, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> On Sep 3, 2:01 pm, Stephan Herrmann <stephan.herrm...@onlinehome.de>
> wrote:
>
> > 1. observation: the field 'current' had to be split into 2:
> > Intersection currentIntersection;
> > DistanceLabeledGraphNode current;
> > the former is used for finding neighbors, the latter for computing
> > distances.
>
> I have a problem here,

Would you prefer this:

Intersection currentIntersection;
DistanceLabeledGraphNode currentDLGN() {
return asDLGN(currentIntersection);
}

This would look nicer if Java had a notion of derived attributes.

My implementation implicitly adheres to the discipline that both
fields hold roles of the same Node. With this discipline both forms
are actually equivalent, but the form using only one field plus one
method is more explicit in this regard. Take your choice :)

cheers,
Stephan

Ant Kutschera

unread,
Sep 6, 2011, 1:48:40 PM9/6/11
to object-composition
On Sep 2, 10:49 am, "James O. Coplien" <jcopl...@gmail.com> wrote:
> > I have been thinking about identity and equality, and how I use them
> > in day to day life.  Since we send object from clients to servers and
> > back again, we never ever use the Java == to test identity.  We ALWAYS
> > rely on the equals method and hashCode methods.
>
> Right — the SOA approach, more or less, right? I am with you and think that I understand your rationalization.

Yes, it's true that it is only object state which is transferred.

But forget client/server apps. We are still encouraged to never ever
use ==, unless it is for optimisation purposes. Perhaps that is
because of Sun's SOA approach to EJB, or perhaps it stems from other
reasons - I don't know the origin.

Ant Kutschera

unread,
Sep 6, 2011, 2:03:00 PM9/6/11
to object-composition
On Sep 6, 1:46 pm, Stephan Herrmann <stephan.herrm...@onlinehome.de>
wrote:
> From what you wrote I get the impression you are very keen on
> separation at the side of *specifying* a concept, but then you don't
> seem to *use* the name you gave to that concept.

Yes, because I am struggling with finding a suitable name for
something that can play all three of these roles. I only went down
the route of creating a mega interface, because I wanted to program
something in Java similar to what James programmed in Ruby.

I guess the point is, in dynamically typed languages, you only need to
think of a variable name, so you refer to your object as say a
neighbour. In that dynamically typed language, there is then no need
to cast, because the object is playing all those roles already.

In a statically typed language, you need to create a typed variable,
so you need to choose the interface, as well as a name.

Perhaps, choosing the name is like a sublte cast, and so perhaps, it
would be equally valid to just cast the object to what ever role you
need in that instance.

When I first programmed it that way, I thought it looked messy. Now
that I think about it more, perhaps this quick fire recasting isn't so
bad, because like you say, the mental model switches the objects role
from time to time, as seen by the name given to the variable.

I'll think more, and compare my solution that has a mega interface, to
one that doesn't, and see which I prefer...

What I can say, is that in my Till example, I really don't find my
mental model merging the sales log (role) and printer (role) into
one. They are seperate roles, played by the same object (basket), but
do very different things.

Ant

Ant Kutschera

unread,
Sep 6, 2011, 2:05:18 PM9/6/11
to object-composition
On Sep 6, 2:01 pm, Stephan Herrmann <stephan.herrm...@onlinehome.de>
wrote:
> Would you prefer this:
>
>        Intersection currentIntersection;
>        DistanceLabeledGraphNode currentDLGN() {
>            return asDLGN(currentIntersection);
>        }
>

That is just a quick re-cast.

In a way I do prefer that, because holding two references to something
that is meant to be one and the same thing feels like it has been
split in two.

Before your previous post, I thought I would prefer a merged
interface. Now I don't know anymore and need to think about it :-)

rune funch

unread,
Sep 8, 2011, 11:28:42 AM9/8/11
to object-co...@googlegroups.com
just for completeness when it comes to the debate of whether interfaces are needed in strongly typed languages I've written the good ol' and trusted in F# with no use of interfaces

//Just a type that can be used
type Account(balance : decimal) = 
   let balance = balance
   member x.IncreaseBalance (amount:decimal) = new Account(balance + amount)
   member x.DecreaseBalance (amount:decimal) = x.IncreaseBalance (0m-amount)
   member x.LogMessage (message:string) = System.Console.WriteLine message

//Generic role only usages is to declare the -~> operator
type Role(self) = 
      member x.self = self
      static member (-~>) ((this:Role), func) = func this.self


type Context(source, destination, IncreaseBalance, DecreaseBalance, LogMessage) =
   let source = Role(source)
   let destination = Role(destination)
   
   member x.Transfer amount =
      let src = 
                 source-~>LogMessage (sprintf "Balance decreased by %M" amount)
                 source-~>DecreaseBalance amount
      let dst = 
                 destination-~>LogMessage (sprintf "Balance increased by %M" amount)
                 destination-~>IncreaseBalance amount
      (src,dst)

//member constraints can only be declared on inlined functions. Inlined functions can't be part of a type
let inline createContext source destination  =
    let increaseBalance = (fun amount (s:^T) -> (^T : (member DecreaseBalance : ^b -> ^a) (s, amount)))
    let decreaseBalance = (fun amount (d:^T) -> (^T : (member IncreaseBalance : ^b -> ^a) (d, amount)))
    let (log:string->Account->unit) = (fun msg a -> (^T : (member LogMessage : string -> unit) (a, msg)))
    let f = fun amount -> increaseBalance amount source |> ignore
                          log "" source |> ignore
    let f = fun amount -> decreaseBalance amount destination |> ignore
                          log "" destination |> ignore
    new Context (source, destination, increaseBalance, decreaseBalance, log)
let ctxt = createContext (new Account 0m) (new Account 0m)
ctxt.Transfer(100m) |> ignore


I have multiple issues with this implementation seen from a DCI perspective but the idea was to show that it's indeed possible to use a strongly typed language an no interfaces for data and still have them play nice when bound to a role. The main issue I have is that I can't inline the type definition (something you can do in c++ with templates) this creates the need for the last three arguments on the context. They are all rather weird and could basically rename the method called on the RolePlayer which would be rather surprising to most I guess

2011/9/4 Ant Kutschera <ant.ku...@gmail.com>

--

Ant Kutschera

unread,
Sep 9, 2011, 4:43:02 AM9/9/11
to object-composition
You might not be using a strict interface, but are you not implicitly
defining the "interface" which an object requires to be used?
> 2011/9/4 Ant Kutschera <ant.kutsch...@gmail.com>

Ant Kutschera

unread,
Sep 9, 2011, 4:44:08 AM9/9/11
to object-composition
OK, so I have my code finished - it uses wrappers, and my Behaviour
Injector, which leverages reflection to make the wrappers easier to
write.

I have two solutions.

The first one, uses a mega-pimped wrapper. That means, that at any
time in the code, I can call any of the role methods from any of the
three roles Neighbor, DistanceLabelledGraphNode or
CurrentIntersection, or indeed any of the methods from the object
playing the role. This can also be called the "merged interface".
Defining it is as simple as:

interface Mega extends Distance_labeled_graph_node,
CurrentIntersection, Neighbor, Node, Unassignable {}

I called it Mega, partly because I have no idea what else to call it.
Perhaps DijkstraNode might be better. Notice how its interface is
made of three roles, an interface called Node (which is the interface
of the original plain and simple NodeImpl class), and then finally the
Unassignable interface. This last interface is specific to my
BehaviourInjector, and I shall explain it later, but it lets anyone
using Mega objects access the wrapped object.

The second solution doesn't use a merged interface, but simply casts
an object into a role at any time it needs to, either in the context,
or indeed inside role methods. That means, I end up with code like
this in many places in the code:

Distance_labeled_graph_node dn = assignRole(intersection,
Distance_labeled_graph_node.class);

Whether to use one merged interface and avoid casting on the fly, or
to use single role interfaces and constantly cast from one role into
another, as required, seems to be a long discussion on its own. On
the one hand, I have argued that I prefer a merged interface because
continuously (re)casting seems over complicated. But then as Stephan
pointed out, the name you choose for the variable is a point in the
mental model, where you are effectively casting, because on that line
of code, you are saying what you intend the object to be capable of.
Here are two examples of James code:

unvisited_neighbors.each { |neighbor|
...

unvisited.each_key { |intersection|
...

In these snippets of code, James is saying that he is thinking of the
nodes as neighbors or intersections respectively, because that is the
name he gives them (between the pipes). If I am using wrappers and a
statically typed language such as Java, and NO merged interface, I
could simply add a line of code in places like that, to cast a role.
I don't think it is that bad, either.

Then again, here is another line of code from James:

if intersection.tentative_distance < min
...

Here, he is referring to the "intersection" actor and asking it to
provide it's tentative distance, which is a role method from the role
Distance_Labeled_Graph_Node.

In a way, that is like asking Big Momma to recite lines from Big
Poppa's script, just because they are both being played by Eddie
Murphy... This is one of the reason's I really don't like dynamic
languages, and why I don't like them for DCI, because it is so easy to
confused what the object really is, and no IDE can help you figure it
out, unless you use the debugger.

In my code below, I have a similar predicament, because James wants to
get and set tentative distances (DistanceLabeledGraphNode role) in the
Neighbor role:

//a role implementation...
public static class NeighborImpl {

@Self
Mega self; //interesting... self is a Mega, not a Neighbor...




So, let's return to the original discussion, which was whether
wrappers could be used to implement the Dijkstra Algorithm, in a
similar way to which James did it in Ruby. The steps which I had to
take to make my wrappers solution work as seamlessly as possible, are
as follows:

1) Create an interface for the plain object, that declares the methods
it exposes. We do this, so that the wrapper can pretend to be the
original object. All calls to such methods are passed straight to the
wrapped object. An example is where we restart the context while
recursing. To do that, we need to pass an object of type Node to the
context, but happen to have a wrapper (role) in our hands. We can
either unassign it (using the Unassign interface from my Behaviour
Injector), or, just pass the wrapper, because it now has the Node
interface.

2) Get the Behaviour Injector to pass all calls to the equals(Object)
and hashCode() methods, to the original object. That way, when
putting the wrapper into an associative array or hash based object, it
would be stored in the same location as the original object would be.
The associative array would be fooled into thinking the wrapper was
the original object. Perhaps we have some luck, that in Java, the
Java API never relies solely on ==, but always on the equals(Object)
and hashCode() methods. Like I have said previously, in Java we are
positively encouraged to never ever use the == operator, unless it is
for optimisation purposes.

3) By getting the Behaviour Injector to pass calls to the
equals(Object) and hashCode() methods, we can fool the system into
thinking the wrapper is the original object, but we struggle to fool
it that the original object is the wrapper. Take these lines of code:

Object o = ...
Role r = createWrapper(o, Role.class);
r.equals(o); //true, because the wrapper passes the call to the
object it wraps
o.equals(r); //false!

To solve the problem that o is not equal to r, we simply modify how we
implemented the equals method in the original object. Sadly it builds
reliance on our framework into the "data" part of the system, but hey,
life isn't always peachy:

public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass()){
if(obj instanceof Unassignable){
// UNASSIGN AND RESTART EQUALITY CHECK
NodeImpl n = ((Unassignable<NodeImpl>)obj).unassign();
return equals(n);
}else{
return false;
}
}
NodeImpl other = (NodeImpl) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}

See in the middle, where we unassign and restart the equality check?

4) Roles cannot contain state. James tried it out in his Ruby code,
and so far as I can tell, wrappers can't cope with it. The problem
is, if a wrapper contains state, you can only wrap the original object
one time. If you wrap it again later, you lose that original state.
So, where can you put the code which wraps it just once? Sure, you
could do that in the context. That means as soon as you enter the
context, you wrap the objects - all of them! Then what do you do with
the references (pointers) to those wrapped objects? You need to store
them somewhere. OK, you could store them in the context. What
happens when you then get a plain object later on in the code, because
for example you are calling deep into the data model to get the next
east neighbor to the current node? In order to get the role state,
you need to locate the wrapper which corresponds to that object you
just received from deep in the model. Do you look that wrapper up in
the context? Underwear hanging out, because no where in the use case,
does it say that you need to lookup the wrapper in order to find the
state! The easiest thing to do is to not keep state in roles. Then,
like James suggested in the comment in his code, you use an
associative array in the context to store the new state, and the key
is the wrapper, or the original object being wrapped (it doesn't
matter which, because they are equal and have the same hashcode). I
don't think it is wrong to keep the role's state in the context,
because since I first looked at DCI, it has always been said that
roles are stateless. And for over a decade of Sun's EJB (SOA) model,
it has always been said that services should be stateless (Stateful
EJBs are the devil's work and never get used!).

5) For similar reasons, the rebind() methods from James' code are not
present in my code. Calling a method which returns void is useless
for wrappers, because you might wrap the objects, but what do you do
with your reference (pointer) to the wrapper? If you don't have a
suitable place to stick it in the context, you are forced to stick it
in the bin. If you create a place for it in the context, just because
you are using wrappers, your underwear hangs out.

So, I don't think my two solutions look that bad. I feel like I have
overcome all of the problems of object schizophrenia which this
problem has posed to me, in fairly elegant ways. I don't look at any
of my code and see that I have to be careful of surprises. Comparing
my code to James' Ruby Code, it reads pretty much one to one, when you
look at the solution which uses a merged interface.

While the five steps listed above might sound like a lot of work, most
of them are things I am already used to having to do, just because I
program enterprise systems in Java:

- work with interfaces to my data: see EMF from Eclipse as an
example, where this is common practise;
- implement equals and hashcode methods in all model objects,
myself - I already do that in all my code anyway;
- stateless places where system behaviour is programmed - I do
that in services already;

Sure, it's not ideal from a DCI certification perspective. But like I
pointed out above, and with previous questions to James - some of his
code is a little dubious too, and I don't think it is fair to say that
the Ruby code is a reference implementation which qualifies for DCI
certification. Two problems with the approach using dynamically typed
language are, in my opinion:

1) Unassignment of roles after the context has completed. Can it be
done automatically? If not, you are passing objects around after the
context, which may still have role methods hanging off them.
2) Runtime assignment of roles means it may not be possible to
determine statically if an object is playing a role or not. With
statically typed languages, you have a statically typed reference in
your hand when you use an object, so you know exactly what it is
capable of. See my "devil" example in this thread a few posts ago.

My two solutions follow. First, there is all the common stuff. Then,
the solution with NO merged interface, and finally, the solution WITH
a merged interface, albeit poorly named (Mega).

PS - To reduce code duplication, I moved the duplicated roles into the
abstract context... just to see what it looked like...

PPS - Just as a reminder how my BehaviourInjector works: 1) you define
an interface which the role should have; 2) you use an annotation to
tell it where the implementation is to be found; 3) you create a class
containing the role implementation; 4) in your context, you use the
BehaviourInjector class to "inject behaviour", which causes it to
create a dynamic proxy containing a handler which hands calls off to
either the original object, or the role implementation(s). As such,
it's a wrapper, but it leverages reflection so that a lot of the
boilerplate stuff is done transparently. See www.maxant.co.uk/tools.jsp
for info - warning, these samples below require version 1.3, which is
currently unavailable...

------------------------------------------------------------------
------ DATA MODEL, AND OTHER STUFF - SAME FOR BOTH SOLUTIONS ------
------------------------------------------------------------------

package data;
public class Pair {
private Object a;
private Object b;

public Pair(Object a, Object b) {
this.a = a;
this.b = b;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((a == null) ? 0 : a.hashCode());
result = prime * result + ((b == null) ? 0 : b.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Pair other = (Pair) obj;
if (a == null) {
if (other.a != null)
return false;
} else if (!a.equals(other.a))
return false;
if (b == null) {
if (other.b != null)
return false;
} else if (!b.equals(other.b))
return false;
return true;
}

@Override
public String toString() {
return "Pair [" + a + ", " + b + "]";
}

}

------------------------------------------------------------------

package data;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class Geometry {

List<Node> nodes;
Node root;
Node destination;
Map<Pair, Integer> distances;
Map<Node, Node> next_down_the_street_from = new HashMap<Node,
Node>();
Map<Node, Node> next_along_the_avenue_from = new HashMap<Node,
Node>();

public Node east_neighbor_of(Node a) {
return next_down_the_street_from.get(a);
}

public Node south_neighborOf(Node a) {
return next_along_the_avenue_from.get(a);
}

public Node root() {
return root;
}

public Node destination() {
return destination;
}

public List<Node> nodes() {
return nodes;
}

public Node getRoot() {
return root;
}

public Node getDestination() {
return destination;
}

public List<Node> getNodes() {
return nodes;
}

public Integer getDistance(Node a, Node b) {
return distances.get(new Pair(a, b));
}
}

------------------------------------------------------------------

package data;

/** Data interface - used so that roles can implement this interface
and we don't
* need to unassign, before restarting the context when recursing. */
public interface Node {

public String getName();
}

------------------------------------------------------------------

package data;

import ch.maxant.dci.util.Unassignable;

/** Data */
public class NodeImpl implements Node {

private String name;

public NodeImpl(String name) {
this.name = name;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 :
name.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass()){
if(obj instanceof Unassignable){
@SuppressWarnings("unchecked")
NodeImpl n = ((Unassignable<NodeImpl>)obj).unassign();
return equals(n);
}else{
return false;
}
}
NodeImpl other = (NodeImpl) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}

public String getName() {
return name;
}

/** only done for debugging purposes */
@Override
public String toString() {
return "Node[name=" + name + ", hashCode=" + hashCode() + "]";
}
}

------------------------------------------------------------------

package data;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

import static common.Runner.INFINITY;

public class ManhattanGeometry1 extends Geometry {

public ManhattanGeometry1() {
this.nodes = new ArrayList<Node>();
this.distances = new HashMap<Pair, Integer>();

// I HAD TO ENSURE NAMES ARE UNIQUE. JAMES SAYS THAT MAKES
THE SOLUTION NON-DCI-CERTIFIABLE...

String[] names = { "a", "b", "c", "d", "e", "f", "g", "h",
"i" };

for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
this.nodes.add(new NodeImpl(names[(i * 3) + j]));
}
}

// Aliases to help set up the grid. Grid is of Manhattan form:
//
// a - 2 - b - 3 - c
// | | |
// 1 2 1
// | | |
// d - 1 - e - 1 - f
// | |
// 2 4
// | |
// g - 1 - h - 2 - i
//
Node a = this.nodes.get(0);
root = a;
Node b = this.nodes.get(1);
Node c = this.nodes.get(2);
Node d = this.nodes.get(3);
Node e = this.nodes.get(4);
Node f = this.nodes.get(5);
Node g = this.nodes.get(6);
Node h = this.nodes.get(7);
Node i = this.nodes.get(8);
destination = i;

for (int s = 0; s < 3; s++) {
for (int t = 0; t < 3; t++) {
this.distances.put(new Pair(nodes.get(s),
nodes.get(t)), INFINITY);
}
}

distances.put(new Pair(a, b), 2);
distances.put(new Pair(b, c), 3);
distances.put(new Pair(c, f), 1);
distances.put(new Pair(f, i), 4);
distances.put(new Pair(b, e), 2);
distances.put(new Pair(e, f), 1);
distances.put(new Pair(a, d), 1);
distances.put(new Pair(d, g), 2);
distances.put(new Pair(g, h), 1);
distances.put(new Pair(h, i), 2);
distances.put(new Pair(d, e), 1);

distances = Collections.unmodifiableMap(distances);

next_down_the_street_from.put(a, b);
next_down_the_street_from.put(b, c);
next_down_the_street_from.put(d, e);
next_down_the_street_from.put(e, f);
next_down_the_street_from.put(g, h);
next_down_the_street_from.put(h, i);
next_down_the_street_from = Collections
.unmodifiableMap(next_down_the_street_from);

next_along_the_avenue_from.put(a, d);
next_along_the_avenue_from.put(b, e);
next_along_the_avenue_from.put(c, f);
next_along_the_avenue_from.put(d, g);
next_along_the_avenue_from.put(f, i);

next_along_the_avenue_from = Collections
.unmodifiableMap(next_along_the_avenue_from);
}

}

------------------------------------------------------------------

package data;

import static common.Runner.INFINITY;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

public class ManhattanGeometry2 extends Geometry {

public ManhattanGeometry2() {
this.nodes = new ArrayList<Node>();
this.distances = new HashMap<Pair, Integer>();

// I HAD TO ENSURE NAMES ARE UNIQUE. JAMES SAYS THAT MAKES
THE SOLUTION NON-DCI-CERTIFIABLE...

String[] names = { "a", "b", "c", "d", "e", "f", "g", "h",
"i", "j", "k" };

for (int j = 0; j < 11; j++) {
nodes.add(new NodeImpl(names[j]));
}

// Aliases to help set up the grid. Grid is of Manhattan form:
//
// a - 2 - b - 3 - c - 1 - j
// | | | |
// 1 2 1 |
// | | | |
// d - 1 - e - 1 - f 1
// | | |
// 2 4 |
// | | |
// g - 1 - h - 2 - i - 2 - k
//
Node a = nodes.get(0);
root = a;
Node b = nodes.get(1);
Node c = nodes.get(2);
Node d = nodes.get(3);
Node e = nodes.get(4);
Node f = nodes.get(5);
Node g = nodes.get(6);
Node h = nodes.get(7);
Node i = nodes.get(8);
Node j = nodes.get(9);
Node k = nodes.get(10);
destination = k;

for (int s = 0; s < 11; s++) {
for (int t = 0; t < 11; t++) {
distances.put(new Pair(nodes.get(s), nodes.get(t)),
INFINITY);
}
}

distances.put(new Pair(a, b), 2);
distances.put(new Pair(b, c), 3);
distances.put(new Pair(c, f), 1);
distances.put(new Pair(f, i), 4);
distances.put(new Pair(b, e), 2);
distances.put(new Pair(e, f), 1);
distances.put(new Pair(a, d), 1);
distances.put(new Pair(d, g), 2);
distances.put(new Pair(g, h), 1);
distances.put(new Pair(h, i), 2);
distances.put(new Pair(d, e), 1);
distances.put(new Pair(c, j), 1);
distances.put(new Pair(j, k), 1);
distances.put(new Pair(i, k), 2);

distances = Collections.unmodifiableMap(distances);

next_down_the_street_from.put(a, b);
next_down_the_street_from.put(b, c);
next_down_the_street_from.put(c, j);
next_down_the_street_from.put(d, e);
next_down_the_street_from.put(e, f);
next_down_the_street_from.put(g, h);
next_down_the_street_from.put(h, i);
next_down_the_street_from.put(i, k);

next_down_the_street_from = Collections
.unmodifiableMap(next_down_the_street_from);

next_along_the_avenue_from.put(a, d);
next_along_the_avenue_from.put(b, e);
next_along_the_avenue_from.put(c, f);
next_along_the_avenue_from.put(d, g);
next_along_the_avenue_from.put(f, i);
next_along_the_avenue_from.put(j, k);

next_along_the_avenue_from = Collections
.unmodifiableMap(next_along_the_avenue_from);
}

}

------------------------------------------------------------------

package common;
import shortestdistance.CalculateShortestDistance;
import shortestpath.CalculateShortestPath;
import data.Geometry;
import data.ManhattanGeometry1;
import data.ManhattanGeometry2;
import data.Node;

public class Runner {

/**
* use less than infinity, otherwise some of the calcs go wrong,
* when we add tentative distances to actual distances and we end
* up with negative numbers, because weve gone over max INT.
*/
public static final Integer INFINITY = Integer.MAX_VALUE - 10000;

/** Test drivers */
public static void main(String[] args) {

Geometry geometries = new ManhattanGeometry1();

CalculateShortestPath path = new
CalculateShortestPath(geometries.getRoot(),
geometries.getDestination(), geometries, null, null,
null);

System.out.println("Path is: ");
for (Node node : path.getPath()) {
System.out.println(node.getName());
}

System.out.println("distance is "
+ new CalculateShortestDistance(geometries.getRoot(),
geometries.getDestination(),
geometries).distance());

System.out.println();

geometries = new ManhattanGeometry2();

path = new CalculateShortestPath(geometries.getRoot(),
geometries.getDestination(), geometries, null, null,
null);

System.out.println("Path is: ");
Node last_node = null;
for (Node node : path.getPath()) {
if (last_node != null) {
System.out.print(" - "
+ geometries.getDistance(node, last_node)
+ " - ");
}
System.out.print(node.getName());
last_node = node;
}

System.out.println();
System.out.println("distance is "
+ new CalculateShortestDistance(geometries.getRoot(),
geometries.getDestination(),
geometries).distance());
}

}

------------------------------------------------------------------
------ SOLUTION (1) - NO MERGED INTERFACES -----------------------
------------------------------------------------------------------

package common;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import ch.maxant.dci.util.BehaviourInjector;
import ch.maxant.dci.util.Context;
import ch.maxant.dci.util.Data;
import ch.maxant.dci.util.Role;
import ch.maxant.dci.util.Self;
import ch.maxant.dci.util.Unassignable;
import data.Geometry;
import data.Node;

/**
* This is an abstract context. it contains a roles which are used in
* several places.
*/
@Context
public class AbstractCalculateShortestX extends BehaviourInjector {

//****************
//context state:
//****************
protected static Map<Node, Integer> tentativeDistances = new
HashMap<Node, Integer>();

//****************
//role interfaces:
//****************

@Role(implementationClass = Distance_labeled_graph_nodeImpl.class)
public interface Distance_labeled_graph_node extends Node,
Unassignable<Node> {
void set_tentative_distance_to(Integer x);
int get_tentative_distance_to();
}

@Role(implementationClass = CartographyMapImpl.class)
protected interface CartographyMap extends Unassignable<Geometry>
{
Node south_neighborOf(Node n);
List<Node> getNodes();
Node east_neighbor_of(Node n);
Integer distance_between(Node a, Node b);
Node next_down_the_street_from(Node n);
Node next_along_the_avenue_from(Node n);
Integer getDistance(Node a, Node b);
}

//****************
//role implementations:
//****************

/**
* There are four roles in the algorithm: CurrentIntersection
(@current)
* EastNeighbor, which lies DIRECTLY to the east of
CurrentIntersection
* (@east_neighbor) SouthernNeighbor, which is DIRECTLy to its
south
* (@south_neighbor) Destination, the target node (@destination)
*
* We also add a role of Map (@map) as the oracle for the geometry
*
* The algorithm is straight from Wikipedia:
*
* http://en.wikipedia.org/wiki/Dijkstra's_algorithm
*
* and reads directly from the distance method, below
*/
public static class Distance_labeled_graph_nodeImpl {

@Data
Node self;

@Resource(name="actualContext")
CalculateShortestPath context;

void set_tentative_distance_to(Integer x) {
context.tentativeDistances.put(self, x);
}

Integer get_tentative_distance_to(){
return context.tentativeDistances.get(self);
}

}

public static class CartographyMapImpl {

@Self
CartographyMap self;

Integer distance_between(Node a, Node b) {
//behaviour injector does not allow roles to
//directly access fields - we have to make
//a method call here
return self.getDistance(a, b);
}

// These two functions presume always travelling
// in a southern or easterly direction

Node next_down_the_street_from(Node node) {
return self.east_neighbor_of(node);
}

Node next_along_the_avenue_from(Node node) {
return self.south_neighborOf(node);
}

}
}

------------------------------------------------------------------

package shortestpath;

import static common.Runner.INFINITY;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import ch.maxant.dci.util.Context;
import ch.maxant.dci.util.Role;
import ch.maxant.dci.util.Self;
import ch.maxant.dci.util.Unassignable;

import common.AbstractCalculateShortestX;

import data.Geometry;
import data.Node;

/**
* this is a context
*/
@Context
public class CalculateShortestPath extends AbstractCalculateShortestX
{

//****************
//context state:
//****************

Map<Node, Boolean> unvisited = new HashMap<Node, Boolean>();
Map<Neighbor, Neighbor> pathTo;
Neighbor east_neighbor;
Neighbor south_neighbor;
List<Node> path;
CartographyMap map;
CurrentIntersection current;
Node destination;

//****************
//role interfaces:
//****************

@Role(contextClass = CalculateShortestPath.class,
implementationClass = CurrentIntersectionImpl.class)
interface CurrentIntersection extends Node, Unassignable<Node> {
List<Neighbor> unvisited_neighbors();
}

/**
* This module serves to provide the methods both for the
east_neighbor and
* south_neighbor roles
*/
@Role(contextClass = CalculateShortestPath.class,
implementationClass = NeighborImpl.class)
interface Neighbor extends Node, Unassignable<Node> {
boolean relable_node_as(int x);
}

//****************
//role implementations:
//****************

public static class CurrentIntersectionImpl {

// Access to roles and other Context data
@Resource(name = "actualContext")
CalculateShortestPath context;

List<Neighbor> unvisited_neighbors() {

//TODO moved access to data from context inside method,
otherwise we are introducing state to the role
Map<Node, Boolean> unvisited = context.unvisited;
Neighbor south_neighbor = context.south_neighbor;
Neighbor east_neighbor = context.east_neighbor;

List<Neighbor> retval = new ArrayList<Neighbor>();
if (south_neighbor != null) {
Boolean addIt = unvisited.get(south_neighbor);
if (addIt == Boolean.TRUE) { //watch out, addIt can be
null apparently
retval.add(south_neighbor);
}
}
if (east_neighbor != null) {
Boolean addIt = unvisited.get(east_neighbor);
if (addIt == Boolean.TRUE) { //watch out, addIt can be
null apparently
retval.add(east_neighbor);
}

}
return retval;
}
}

public static class NeighborImpl {

@Self
Neighbor self;

@Resource(name="actualContext")
CalculateShortestPath context;

boolean relable_node_as(int x) {
if (x < context.tentativeDistances.get(self)) {

// we quickly reassign the role, because we need to
add
// functionality and only know this object in the
neighbor role
context.assignRole(self,
Distance_labeled_graph_node.class)
.set_tentative_distance_to(x);
return true;
} else {
return false;
}
}

}

//****************
//context implementations:
//****************

/**
* public initialize. It's overloaded so that the public version
doesn't
* have to pass a lot of crap; the initialize method takes care of
setting
* up internal data structures on the first invocation. On
recursion we
* override the defaults
*/
public CalculateShortestPath(Node origin_node, Node target_node,
Geometry geometries, List<Node> path_vector,
Map<Node, Boolean> unvisited_hash,
Map<Neighbor, Neighbor> pathto_hash) {

this.addResource("actualContext", this);

this.destination = target_node;

this.map = assignRole(geometries, CartographyMap.class);
this.current = assignRole(origin_node,
CurrentIntersection.class);

Node en = map.east_neighbor_of(origin_node);
if (en != null) {
this.east_neighbor = assignRole(en, Neighbor.class);
}

Node sn = map.south_neighborOf(origin_node);
if (sn != null) {
this.south_neighbor = assignRole(sn, Neighbor.class);
}

// This has to come after rebind is done
if (path_vector == null) {

// This is the fundamental data structure for Dijkstra's
algorithm,
// called
// "Q" in the Wikipedia description. It is a boolean hash
that maps
// a
// node onto false or true according to whether it has
been visited
this.unvisited = new HashMap<Node, Boolean>();

// These initializations are directly from the description
of the
// algorithm
for (Node n : geometries.getNodes()) {
this.unvisited.put(n, Boolean.TRUE);
}

this.unvisited.remove(origin_node);

for (Object n : getIterable(map.getNodes().iterator(),
Distance_labeled_graph_node.class)) {
// TODO why is this java cast needed here? it
shouldnt be...
Distance_labeled_graph_node dn =
(Distance_labeled_graph_node) n;
dn.set_tentative_distance_to(INFINITY);
}

assignRole(origin_node, Distance_labeled_graph_node.class)
.set_tentative_distance_to(0);

// The path array is kept in the outermost context and
serves to
// store the
// return path. Each recurring context may add something
to the
// array along
// the way. However, because of the nature of the
algorithm,
// individual
// Context instances don't deliver "partial paths" as
partial
// answers.
this.path = new ArrayList<Node>();

// The pathTo map is a local associative array that
remembers the
// arrows between nodes through the array and erases them
if we
// re-label a node with a shorter distance
this.pathTo = new HashMap<Neighbor, Neighbor>();

} else {

this.unvisited = unvisited_hash;
this.path = path_vector;
this.pathTo = pathto_hash;
}

execute();
}

/**
* This is the method that does the work. Called from initialize
*
* @param current
*/
public void execute() {
// Calculate tentative distances of unvisited neighbors
List<Neighbor> unvisited_neighbors =
current.unvisited_neighbors();
if (unvisited_neighbors != null) {
for (Neighbor neighbor : unvisited_neighbors) {

Integer tentativeDistance =
tentativeDistances.get(current);
Integer distanceBetween =
map.distance_between(current, neighbor);
boolean relable_node_as =
neighbor.relable_node_as(tentativeDistance + distanceBetween);

if (relable_node_as) {

// dodgy? our associative array uses generics and
expects the
// key and value to be of type Neighbor - but
"current" isn't
// playing that role, so we need to do a quick
cast
pathTo.put(neighbor, assignRole(current,
Neighbor.class));
}
}
}

unvisited.remove(current);

// Are we done?

if (unvisited.size() == 0) {
save_path(this.path);
} else {

// The next current node is the one with the least
distance in the
// unvisited set

Node selection = nearest_unvisited_node_to_target();

// Recur
new CalculateShortestPath(selection, destination,
map.unassign(),
path, unvisited, pathTo);
}
}

Node nearest_unvisited_node_to_target() {

int min = INFINITY;
Node selection = null;

for (Node intersection : unvisited.keySet()) {
if (unvisited.get(intersection)) {

// cast into role, to add behaviour - the unvisited
list
// contains plain objects as keys
Distance_labeled_graph_node dn =
assignRole(intersection,
Distance_labeled_graph_node.class);

if (dn.get_tentative_distance_to() <= min) {

min = dn.get_tentative_distance_to();
selection = intersection;
}
}
}
return selection;
}

/**
* This method does a simple traversal of the data structures
(following
* pathTo) to build the directed traversal vector for the minimum
path
*/
void save_path(List<Node> pathVector) {

Node node = destination;
do {
pathVector.add(node);

node = pathTo.get(node);

} while (node != null);
}

public List<Node> getPath() {
//this MUST return node, because the result is used outside
the context.
return path;
}

}

------------------------------------------------------------------
------ SOLUTION (2) - WITH MERGED INTERFACE ----------------------
------------------------------------------------------------------

package common;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import ch.maxant.dci.util.BehaviourInjector;
import ch.maxant.dci.util.Context;
import ch.maxant.dci.util.Role;
import ch.maxant.dci.util.Self;
import ch.maxant.dci.util.Unassignable;
import data.Geometry;
import data.Node;

/**
* This is an abstract context. it contains a role which is used in
* several places.
*/
@Context
public class AbstractCalculateShortestX extends BehaviourInjector {

//****************
//context state:
//****************

//moved out of Distance_labelled_graph_node into context, because
its data and doesnt belong in roles.
protected static Map<Distance_labeled_graph_node, Integer>
tentativeDistances = new HashMap<Distance_labeled_graph_node,
Integer>();

//****************
//role interfaces:
//****************

public interface Distance_labeled_graph_node {
void set_tentative_distance_to(Integer x);
Integer get_tentative_distance_to();
}

@Role(implementationClass = CartographyMapImpl.class)
protected interface CartographyMap extends Unassignable<Geometry>
{
Node south_neighborOf(Node n);
List<Node> getNodes();
Node east_neighbor_of(Node n);
Integer distance_between(Node a, Node b);
Node next_down_the_street_from(Node n);
Node next_along_the_avenue_from(Node n);
Integer getDistance(Node a, Node b);
}

//****************
//role implementations:
//****************

public static class CartographyMapImpl {

@Self
CartographyMap self;

Integer distance_between(Node a, Node b) {
//behaviour injector does not allow roles to
//directly access fields - we have to make
//a method call here
return self.getDistance(a, b);
}

// These two functions presume always travelling
// in a southern or easterly direction

Node next_down_the_street_from(Node node) {
return self.east_neighbor_of(node);
}

Node next_along_the_avenue_from(Node node) {
return self.south_neighborOf(node);
}

}

/**
* There are four roles in the algorithm: CurrentIntersection
(@current)
* EastNeighbor, which lies DIRECTLY to the east of
CurrentIntersection
* (@east_neighbor) SouthernNeighbor, which is DIRECTLy to its
south
* (@south_neighbor) Destination, the target node (@destination)
*
* We also add a role of Map (@map) as the oracle for the geometry
*
* The algorithm is straight from Wikipedia:
*
* http://en.wikipedia.org/wiki/Dijkstra's_algorithm
*
* and reads directly from the distance method, below
*/
public static class Distance_labeled_graph_nodeImpl {

@Self
Distance_labeled_graph_node self;

@Resource(name="actualContext")
AbstractCalculateShortestX context;

void set_tentative_distance_to(Integer x) {
context.tentativeDistances.put(self, x);
}

Integer get_tentative_distance_to(){
return context.tentativeDistances.get(self);
}

}
}

------------------------------------------------------------------

package shortestdistance;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import shortestpath.CalculateShortestPath;
import ch.maxant.dci.util.Context;
import ch.maxant.dci.util.Role;
import ch.maxant.dci.util.Unassignable;

import common.AbstractCalculateShortestX;

import data.Geometry;
import data.Node;

/**
* This is the main Context for shortest distance calculation
*/
@Context
public class CalculateShortestDistance extends
AbstractCalculateShortestX {

//****************
//context state:
//****************

List<Node> path = new ArrayList<Node>();
CartographyMap map;
Node destination;
Mega current;

//****************
//role interfaces:
//****************

@Role(implementationClass = Distance_labeled_graph_nodeImpl.class)
interface Mega extends Distance_labeled_graph_node, Node,
Unassignable<Node> {}

//****************
//context implementations:
//****************

public CalculateShortestDistance(Node origin_node, Node
target_node,
Geometry geometries) {

this.destination = geometries.getDestination();
this.current = assignRole(origin_node, Mega.class);
this.map = assignRole(geometries, CartographyMap.class);

this.current.set_tentative_distance_to(new Integer(0)); //
create an Integer, rather than use int, coz my behaviour injector isnt
so mature, it struggles with autoboxing

this.path = new CalculateShortestPath(this.current,
this.destination, geometries, null, null, null).getPath();
}

public int distance() {
int retval = 0;
Node previous_node = null;

List<Node> reversed = new ArrayList<Node>(path);
Collections.reverse(reversed);

for (Node node : reversed) {

if (previous_node == null) {
retval = 0;
} else {
retval += this.map.distance_between(previous_node,
node);
}
previous_node = node;
}
return retval;
}
}

------------------------------------------------------------------

package shortestpath;

import static common.Runner.INFINITY;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import ch.maxant.dci.util.Context;
import ch.maxant.dci.util.Role;
import ch.maxant.dci.util.Self;
import ch.maxant.dci.util.Unassignable;

import common.AbstractCalculateShortestX;

import data.Geometry;
import data.Node;

/**
* this is a context
*/
@Context
public class CalculateShortestPath extends AbstractCalculateShortestX
{

//****************
//context state:
//****************

Map<Mega, Boolean> unvisited = new HashMap<Mega, Boolean>();
Map<Mega, Mega> pathTo;
Mega east_neighbor;
Mega south_neighbor;
List<Node> path;
CartographyMap map;
Mega current;
Node destination;

//****************
//role interfaces:
//****************

interface CurrentIntersection {
List<Mega> unvisited_neighbors();
}

/**
* This module serves to provide the methods both for the
east_neighbor and
* south_neighbor roles
*/
interface Neighbor {
boolean relable_node_as(int x);
}

/** this is the mega merged interface, allowing me to access
objects in all roles regardless */
@Role(implementationClasses =
{Distance_labeled_graph_nodeImpl.class, CurrentIntersectionImpl.class,
NeighborImpl.class})
interface Mega extends Distance_labeled_graph_node,
CurrentIntersection, Neighbor, Node, Unassignable<Node> {}

//****************
//role implementations:
//****************

public static class CurrentIntersectionImpl {

// Access to roles and other Context data
@Resource(name = "actualContext")
CalculateShortestPath context;

List<Neighbor> unvisited_neighbors() {

//WATCHOUT: moved the access to data from the context,
from outside this method,
//to in inside it, otherwise we are introducing state to
the role
Map<Mega, Boolean> unvisited = context.unvisited;
Neighbor south_neighbor = context.south_neighbor;
Neighbor east_neighbor = context.east_neighbor;

List<Neighbor> retval = new ArrayList<Neighbor>();
if (south_neighbor != null) {
Boolean addIt = unvisited.get(south_neighbor);
if (addIt == Boolean.TRUE) { //watch out, addIt can be
null apparently
retval.add(south_neighbor);
}
}
if (east_neighbor != null) {
Boolean addIt = unvisited.get(east_neighbor);
if (addIt == Boolean.TRUE) { //watch out, addIt can be
null apparently
retval.add(east_neighbor);
}

}
return retval;
}
}

public static class NeighborImpl {

@Self
Mega self; //interesting... self is a Mega, not a Neighbor...

boolean relable_node_as(int x) {
if (x < self.get_tentative_distance_to()) {
self.set_tentative_distance_to(x);
return true;
} else {
return false;
}
}

}

//****************
//context implementations:
//****************

/**
* public initialize. It's overloaded so that the public version
doesn't
* have to pass a lot of crap; the initialize method takes care of
setting
* up internal data structures on the first invocation. On
recursion we
* override the defaults
*/
public CalculateShortestPath(Node origin_node, Node target_node,
Geometry geometries, List<Node> path_vector,
Map<Mega, Boolean> unvisited_hash,
Map<Mega, Mega> pathto_hash) {

this.addResource("actualContext", this);

this.destination = target_node;

this.map = assignRole(geometries, CartographyMap.class);

this.current = assignRole(origin_node, Mega.class);

Node en = map.east_neighbor_of(origin_node);
if (en != null) {
this.east_neighbor = assignRole(en, Mega.class);
}

Node sn = map.south_neighborOf(origin_node);
if (sn != null) {
this.south_neighbor = assignRole(sn, Mega.class);
}

// This has to come after rebind is done
if (path_vector == null) {

// This is the fundamental data structure for Dijkstra's
algorithm,
// called
// "Q" in the Wikipedia description. It is a boolean hash
that maps
// a
// node onto false or true according to whether it has
been visited
this.unvisited = new HashMap<Mega, Boolean>();

// These initializations are directly from the description
of the
// algorithm
for (Node n : geometries.getNodes()) {
Mega m = assignRole(n, Mega.class);
this.unvisited.put(m, Boolean.TRUE);
m.set_tentative_distance_to(INFINITY);
}

current.set_tentative_distance_to(0);

this.unvisited.remove(origin_node);

// The path array is kept in the outermost context and
serves to
// store the
// return path. Each recurring context may add something
to the
// array along
// the way. However, because of the nature of the
algorithm,
// individual
// Context instances don't deliver "partial paths" as
partial
// answers.
this.path = new ArrayList<Node>();

// The pathTo map is a local associative array that
remembers the
// arrows between nodes through the array and erases them
if we
// re-label a node with a shorter distance
this.pathTo = new HashMap<Mega, Mega>();

} else {

this.unvisited = unvisited_hash;
this.path = path_vector;
this.pathTo = pathto_hash;
}

execute();
}

/**
* This is the method that does the work. Called from initialize
*
* @param current
*/
public void execute() {
// Calculate tentative distances of unvisited neighbors
List<Mega> unvisited_neighbors =
current.unvisited_neighbors();
if (unvisited_neighbors != null) {
for (Mega neighbor : unvisited_neighbors) {

Integer tentativeDistance =
current.get_tentative_distance_to();
Integer distanceBetween =
map.distance_between(current, neighbor);
boolean relable_node_as =
neighbor.relable_node_as(tentativeDistance + distanceBetween);

if (relable_node_as) {
pathTo.put(neighbor, current);
}
}
}

unvisited.remove(current);

// Are we done?

if (unvisited.size() == 0) {
save_path(this.path);
} else {

// The next current node is the one with the least
distance in the
// unvisited set

Node selection = nearest_unvisited_node_to_target();

// Recur
new CalculateShortestPath(selection, destination,
map.unassign(),
path, unvisited, pathTo);
}
}

Node nearest_unvisited_node_to_target() {

int min = INFINITY;
Node selection = null;

for (Mega intersection : unvisited.keySet()) {
if (unvisited.get(intersection)) {

if (intersection.get_tentative_distance_to() <= min) {

min = intersection.get_tentative_distance_to();
selection = intersection;
}
}
}
return selection;
}

/**
* This method does a simple traversal of the data structures
(following
* pathTo) to build the directed traversal vector for the minimum
path
*/
void save_path(List<Node> pathVector) {

Node node = destination;
do {
pathVector.add(node);
node = pathTo.get(node);

} while (node != null);
}

public List<Node> getPath() {
//this MUST return node, because the result is used outside
the context.
return path;
}

}

rune funch

unread,
Sep 9, 2011, 4:47:41 AM9/9/11
to object-co...@googlegroups.com
> You might not be using a strict interface, but are you not implicitly
> defining the "interface" which an object requires to be used?
No I'm explicitly declaring it the difference is that it's not part of
the data but (ideally but impossible with inline in F#) part of the
context, so there's no dependency from what the system is to what the
system does there's only a dependency in the other direction. With
interfaces you have dependencies in both directions.

/Rune

Ant Kutschera

unread,
Sep 9, 2011, 4:16:05 PM9/9/11
to object-composition
On Sep 9, 10:44 am, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> Here, he is referring to the "intersection" actor and asking it to
> provide it's tentative distance, which is a role method from the role
> Distance_Labeled_Graph_Node.
>
> In a way, that is like asking Big Momma to recite lines from Big
> Poppa's script, just because they are both being played by Eddie
> Murphy...  

Oops, I meant Sherman Klump & Buddy Love both being played by Eddie
Murphy in the film "The Nutty Professor" ;-)

Ant Kutschera

unread,
Sep 12, 2011, 3:34:24 AM9/12/11
to object-composition
While not explicitly mentioning the word "schizophrenia", the paper
[1] "A Distributed Object Model for the Java System" by Ann Wollrath,
Roger Riggs, and Jim Waldo
of Sun Microsystems, Inc. (Proceedings of the USENIX 1996 Conference
on Object-Oriented Technologies) talks about the same problems which
we are faced with by using wrappers. That is, that the object in hand
is not the actual object we are thinking about, and as such the
default implementations for the methods of class
Object (equals, hashCode, toString) must be overriden. In this paper
they talk about providing implementations for remote objects. With
DCI Wrappers, we must provide implementations which support using
wrappers (i.e. which call through to the wrapped object). In the same
way that remote objects can be used as keys in associative arrays, so
can wrappers, as I have shown with my code in this thread.

[1] http://pdos.csail.mit.edu/6.824/papers/waldo-rmi.pdf

So (a) we are not the only people facing these problems, and (b) there
are solutions. Perhaps Java's RMI API is one reason why we have
always been encouraged not to use the "==" operator in Java.

@James: Have you looked at my solution with the mega interface and
compared it line for line with your Ruby solution? What do you think
- is object schizoprenia as big a deal as you have claimed?

James O. Coplien

unread,
Sep 12, 2011, 9:13:23 PM9/12/11
to object-co...@googlegroups.com

On Sep 9, 2011, at 1:44 , Ant Kutschera wrote:

> 4) Roles cannot contain state. James tried it out in his Ruby code,
> and so far as I can tell, wrappers can't cope with it.


None of my roles contain state. They do access some state of the object with which they are associated.

James O. Coplien

unread,
Sep 12, 2011, 9:37:10 PM9/12/11
to object-co...@googlegroups.com

On Sep 9, 2011, at 1:44 , Ant Kutschera wrote:

I called it Mega, partly because I have no idea what else to call it.


Why not just call it Object and throw away all the information the type system gives you? That's essentially what you've done. "Mega" conveys no domain information: no more or less than "Object" would. I think this code incorporates all the bad parts of dynamic languages without taking any of the good bits...

In my Ruby rendition, current is not of type Object. It just represents the current intersection. Your code loses that design information.

I think your code may be missing a fundamental property of objects: that names and objects are two different things. I want current to be the name of an object with respect to how I treat it in that context. It is not like a FORTRAN "variable" that has to pre-loaded with a lot of methods. It's just a name, not an object. That's what roles are. Your code strikes me as trying to treat current as a variable. Loading up current with all that role information takes away my ability to reason about the code with respect to the spec. It makes the code work, but to me it's fatal from the perspective of code readability.

This is a broader critique than "this is not DCI." One should use the type system to communicate design information. Here, I guess you do that — except the design (being mega-pimped) detracts from understanding rather than adding to it.

It's not about casting; that's class-think rather than object-think.


In a way, that is like asking Big Momma to recite lines from Big
Poppa's script, just because they are both being played by Eddie
Murphy...  This is one of the reason's I really don't like dynamic
languages, and why I don't like them for DCI, because it is so easy to
confused what the object really is, and no IDE can help you figure it
out, unless you use the debugger.


I think this comment makes it clear that you are thinking too much about the classness of a particular object, as if you are nervous about an object being versatile at run-time. This is a paradigm of programming, and we now have a name for it: Restricted OO. It is a valid paradigm for some simple problems. It is not a valid paradigm for the kinds of problems that DCI aims to solve.


To solve the problem that o is not equal to r, we simply modify how we
implemented the equals method in the original object.  Sadly it builds
reliance on our framework into the "data" part of the system, but hey,
life isn't always peachy:


No, this violates the abstraction layer of what the language means by identity. It requires that the programmer in essence re-wire the machine to make the answer (of equality) come out right. That ain't cheatin' fair.

We explored analogous Java solutions in the early days of DCI. In particular, Agata Przybyszewska (are you still, here, Agata?) did some amazing things with Java reflection. It all worked, and I think ended not having wrappers, but required so much abuse of the Java MOP that the code was barely readable. Here, I have the same problem. I shouldn't have to change the definition of identity if I change the object implementation; that violates some pretty serious principles of coupling and cohesion at the level of the programming paradigm. The whole goal of DCI is to avoid such surprises, and I think we crossed that bridge long ago.

Further, with a bit of work I think I could create a masquerade object that would cause the equality test to fail. I don't think it's airtight.

The code deserves points as a toccata, but I think the results head in exactly the opposite directions from where DCI wants to take us. There is no law of programming that says that DCI has to be convenient in Java, and the goal isn't necessarily to force Java into submission to DCI given that one is willing to suffer a certain level of abuse. The study does, perhaps, offer some insight, because it may add clues about how Java could or should be evolved to support DCI. That's a good academic result. But I don't think the adaptations here add anything for the coder; I find this code worse than if I'd done it POJO style. If someone has a Java gun to your head, I think the answer is to use POJO. Until Java evolves into a Full OO language, I think one uses it outside the Restricted OO paradigm at peril to one's self and to the readers of one's code.

James O. Coplien

unread,
Sep 13, 2011, 12:02:30 AM9/13/11
to object-co...@googlegroups.com

On Sep 12, 2011, at 12:34 , Ant Kutschera wrote:

> So (a) we are not the only people facing these problems,

No, some of us have been familiar with them for more than 25 years.


> and (b) there
> are solutions.

Like DCI. But we have yet to see a reasonable, readable solution in Java that communicates design intent in terms of role-based mental models.


> Perhaps Java's RMI API is one reason why we have
> always been encouraged not to use the "==" operator in Java.

I think there is something much deeper going on here, and that it relates to the other problems with Java. I think they cut the reflection API at just the wrong place.


> @James: Have you looked at my solution with the mega interface and
> compared it line for line with your Ruby solution? What do you think
> - is object schizoprenia as big a deal as you have claimed?

Your example convinces me that it's even worse than I had communicated here earlier, because it highlights design articulation issues I had forgotten about from the last time I looked at this some decades ago. Please see my comments in a separate mail.

Stephan Herrmann

unread,
Sep 13, 2011, 9:34:20 AM9/13/11
to object-composition
James, Ant,

I smell a misunderstanding regarding the nature of role
Distance_labeled_graph_node: I see two ways of describing this role,
which lead to incompatible statements in the discussion:

(a) it's a role class introducing a new data member and the
corresponding accessors.

(b) it's a role that injects a data member and the corresponding
accessors into each data object that it is applied to.

With interpretation (a) we regard the role as having state, with (b)
we extend the state of the data object. Note, I'm not speaking about
different technical phenomena, just about different ways to *describe*
the same thing.

With (a) we focus on modularity: everything regarding tentative
distances is defined in role Distance_labeled_graph_node and we need a
Distance_labeled_graph_node in order to access the tentative distance.
I should probably leave the proper explanation/motivation of (b) to
James.

One consequence of the different perspectives: James states that in
the ruby solution 'current' is a 'CurrentIntersection', which within
(a) looks like cheating because also the tentative distance of the
intersection is accessed. However, if we follow (b) we might conclude
that from now on all [1] Node objects have a tentative distance field
and accessors and hence we may safely access these properties on any
node including the current intersection.

If we're holding back for a moment to say what's bad and what's good,
can we agree on disagreeing about (a) vs. (b) as the "natural"
description of the ruby program?

Stephan

[1] "all" is not correct here. How would I best describe the set of
objects that I can ask for their tentative distance?


On Sep 13, 3:13 am, "James O. Coplien" <jcopl...@gmail.com> wrote:
> On Sep 9, 2011, at 1:44 , Ant Kutschera wrote:
>
> > 4) Roles cannot contain state. James tried it out in his Ruby code,
> > and so far as I can tell, wrappers can't cope with it.
>
> None of my roles contain state. They do access some state of the object with which they are associated.


Ant Kutschera

unread,
Sep 13, 2011, 10:02:07 AM9/13/11
to object-composition
On Sep 13, 3:13 am, "James O. Coplien" <jcopl...@gmail.com> wrote:
Either I don't understand Ruby, or your comment confused me:

module Distance_labeled_graph_node

# NOTE: This role creates a new data member in the
node into
# which it is injected. An alernative
implementation would
# be to use a separate associative array

def tentative_distance; @tentative_distance_value end
def set_tentative_distance_to(x);
@tentative_distance_value = x end
end

"This role creates a new data member in the node"

Is a data member not state?

Stephan Herrmann

unread,
Sep 13, 2011, 11:40:33 AM9/13/11
to object-composition
Hi Ant,

On Sep 13, 4:02 pm, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> Either I don't understand Ruby, or your comment confused me:
>
> module Distance_labeled_graph_node
>
> # NOTE: This role creates a new data member in the
> node into
> # which it is injected. An alernative
> implementation would
> # be to use a separate associative array
>
> def tentative_distance; @tentative_distance_value end
> def set_tentative_distance_to(x);
> @tentative_distance_value = x end
> end
>
> "This role creates a new data member in the node"
>
> Is a data member not state?

My first reaction was similar to yours, but then I re-read the full
sentence:

"This role creates a new data member in the node into which it is
injected."

Look at the last 5 words. These lead me to thinking that James looks
at it from perspective (b) (see my previous post).
Since the fields are not actually declared, just used when needed, you
*could* indeed imagine that the data class Node now has a new data
member, or at least all instances that play the role
Distance_labeled_bla do.

I'm not saying that this interpretation improves anything, just that
it could explain the conflict between both of your statement:. If the
data member becomes part of Node, then the role does not have state of
its own, sort-of.

HTH,
Stephan

James O. Coplien

unread,
Sep 13, 2011, 11:48:36 AM9/13/11
to object-co...@googlegroups.com
Back to basics. Think: What is the principle beneath not having data in roles?

Ant Kutschera

unread,
Sep 13, 2011, 4:27:33 PM9/13/11
to object-composition
See my inline comments below:

On Sep 13, 3:37 am, "James O. Coplien" <jcopl...@gmail.com> wrote:
> On Sep 9, 2011, at 1:44 , Ant Kutschera wrote:
>
> > I called it Mega, partly because I have no idea what else to call it.
>
> Why not just call it Object and throw away all the information the type system gives you? That's essentially what you've done. "Mega" conveys no domain information: no more or less than "Object" would. I think this code incorporates all the bad parts of dynamic languages without taking any of the good bits...
>
> In my Ruby rendition, current is not of type Object. It just represents the current intersection. Your code loses that design information.
>
> I think your code may be missing a fundamental property of objects: that names and objects are two different things. I want current to be the name of an object with respect to how I treat it in that context.

I used exactly the same variable names that you did, or at least I
tried, it's possible I made mistakes, but I am not aware of any.
A quick glimpse shows that the only place I used "Mega" was in
declaring the type - which I am forced to do, as I used a statically
typed language.
So I am not sure how my code loses the design information that
"current" represents the current intersection, other than it has the
type "Mega". If that is such a problem, see the other example I
posted, where I did not use the merged-interface. "current" has the
type "CurrentIntersection".

> It is not like a FORTRAN "variable" that has to pre-loaded with a lot of methods. It's just a name, not an object. That's what roles are. Your code strikes me as trying to treat current as a variable. Loading up current with all that role information takes away my ability to reason about the code with respect to the spec. It makes the code work, but to me it's fatal from the perspective of code readability.
>
> This is a broader critique than "this is not DCI." One should use the type system to communicate design information. Here, I guess you do that — except the design (being mega-pimped) detracts from understanding rather than adding to it.
>
> It's not about casting; that's class-think rather than object-think.

I think I understand what you mean. You're saying that by assigning
into all roles at once, the reader looses the ability to reason about
how the object will be used?

For the same reason, I preferred not using a merged-interface, and
that is why I also posted the same example without the merged
interface.

> > In a way, that is like asking Big Momma to recite lines from Big
> > Poppa's script, just because they are both being played by Eddie
> > Murphy... This is one of the reason's I really don't like dynamic
> > languages, and why I don't like them for DCI, because it is so easy to
> > confused what the object really is, and no IDE can help you figure it
> > out, unless you use the debugger.
>
> I think this comment makes it clear that you are thinking too much about the classness of a particular object, as if you are nervous about an object being versatile at run-time. This is a paradigm of programming, and we now have a name for it: Restricted OO. It is a valid paradigm for some simple problems. It is not a valid paradigm for the kinds of problems that DCI aims to solve.

I think it comes more from being used to working with statically typed
languages - not sure though. The point is, I always have to think
about an object's type in order to use it.

For a moment, let's ignore the code in your example. Let's take a
more hypothetical case.
If you have an object called fireman, is it valid to write code like
this:

fireman.cookBacon();

That method comes from the role Chef. By using the name "fireman", my
intention is to signal to the reader that this object is playing the
role Fireman.

I don't like that line of code because it surprises the reader that a
fireman can cook bacon (his suit is too clumsy to turn the bacon with
the required agility thus risking over cooking it). So I would
discourage a programmer from writing code like that if I were
reviewing their DCI code.

Would it be wrong to do so?

(back to your code and my comment: perhaps because you didn't add
state to the role, but to the node, so you didn't mix role behaviours
after all)

> > To solve the problem that o is not equal to r, we simply modify how we
> > implemented the equals method in the original object. Sadly it builds
> > reliance on our framework into the "data" part of the system, but hey,
> > life isn't always peachy:
>
> No, this violates the abstraction layer of what the language means by identity. It requires that the programmer in essence re-wire the machine to make the answer (of equality) come out right. That ain't cheatin' fair.

Agreed. In fact, what I did, was entirely illegal by my current
client's code conventions, which explicitly state that objects can
only be equal if they are of exactly the same type.

> The code deserves points as a toccata, but I think the results head in exactly the opposite directions from where DCI wants to take us. There is no law of programming that says that DCI has to be convenient in Java, and the goal isn't necessarily to force Java into submission to DCI given that one is willing to suffer a certain level of abuse. The study does, perhaps, offer some insight, because it may add clues about how Java could or should be evolved to support DCI. That's a good academic result. But I don't think the adaptations here add anything for the coder; I find this code worse than if I'd done it POJO style. If someone has a Java gun to your head, I think the answer is to use POJO. Until Java evolves into a Full OO language, I think one uses it outside the Restricted OO paradigm at peril to one's self and to the readers of one's code.

We know that Java won't get DCI certification unless it is radically
changed.

But careful please, because I was not thinking of Java when I wrote
this code. My intention was to investigate the question "Can wrappers
be used to implement DCI in a statically typed language".

If you want to give out DCI certification only to those languages
which allow one to implement a solution to Dijkstra's Algorithm in the
way you did it in your Ruby code, then I think the answer is:
"wrappers implemented in a statically typed language can only be used
to implement DCI if you abide by some hairy rules which break the
abstraction layer of what the language means by identity." That
answer can be abreviated to "No".

But... first of all, I didn't like your Ruby example, because it's a
dynamic language and I dislike them because one loses the ability to
get the compiler and tools to help. Wasn't there discussion last year
that the reason polymorphism is bad is because the compiler loses the
ability to do a certain amount of static analysis? Then surely the
same reasoning that forbids polymorphism in DCI would forbid a dynamic
language from gaining DCI certification?

Second of all, in simple cases like my Till example, I think one can
get away with using parts of DCI in Java. The reason is, I have no
use what so ever of object identity. I am assigning roles to my
objects (wrapping objects) in order to add behaviour to them. By
doing that, I am greatly improving what I can do with plain OO. And
perhaps surprising to you, I think I would even let people put such
code into production. But I wouldn't let them call it DCI, and just
as with OT/J, it would be clear what the boundaries were, for example
that identity is not what it appears. OT/J also observes all objects
in a role and never compares some out-of-role with others in-role, so
suffers less from the problems we have highlighted.

Maybe a good question at this point is: "What is the test suite which
a language is required to pass in order to gain full DCI
certification"? But I guess there are still a lot of unanswered
questions lying around, for example, the best way to make behaviour
usable in more than one context. I would still be interested in
hearing about the list of things which are candidates or which are
definites... Do you think creating such a list would add value to the
discussion James?

Ant Kutschera

unread,
Sep 13, 2011, 4:31:32 PM9/13/11
to object-composition
On Sep 13, 5:48 pm, "James O. Coplien" <jcopl...@gmail.com> wrote:
> Back to basics. Think: What is the principle beneath not having data in roles?


OK, I see what you've done. I guess part of the problem I had was
that I'm a Ruby nuby and didn't know exactly what the code was doing.

Until now, we have always said that part DCI is about adding
*behaviour* to objects.

I will have to think about how I feel about adding new fields to
objects. It's certainly interesting.

But either way you look at it, wrappers can't do it ;-) The
restriction on wrappers still applies.

rune funch

unread,
Sep 13, 2011, 4:49:50 PM9/13/11
to object-co...@googlegroups.com

I think it comes more from being used to working with statically typed
languages - not sure though.  The point is, I always have to think
about an object's type in order to use it.

WHy would you have to that? it at least has nothing to do with statically typed languages. Here's a code snippet in a language you don't know that has no explicit type declaration and I'm pretty sure you can tell me what it does. 

let add x y = x + y 

To be honest it can probably do more that what you'd expect but it should be no surprise what the result of add 5 6 is

how about

let rec smallest list min= 
     match list with
     | head::tail -> if head < min then smallest tail head else smallest tail min
     | [] -> min

ok the syntax of that might look unfamiliar but I still think you'd be able to say that from a list it finds the smallest, even if I changed the names of those functions you'd still be able to tell me what the code did. No tell me what the type of the arguments to each are. Hint you can't they are not defined until the functions are called :)
Dynamically or statically you don't need to worry about the type to figure out what the code is doing or even to write working code. True some language requires that you declare a specific type for each parameter to a function, each member and each local. But that's the language it not because it's statically typed


Ant Kutschera

unread,
Sep 13, 2011, 5:24:50 PM9/13/11
to object-composition
Perhaps I mean *strongly* typed?

What I was trying to say was that because the language requires me to
write a type to the left of a variable, I have to think about what
that object is capable of.

rune funch

unread,
Sep 13, 2011, 5:34:38 PM9/13/11
to object-co...@googlegroups.com
> Perhaps I mean *strongly* typed?
>
> What I was trying to say was that because the language requires me to
> write a type to the left of a variable, I have to think about what
> that object is capable of
Doesn't matter. The below code is strongly typed (it's F# again) and
you can write an entire C# program and only without declaring anything
of any explicit type and that's without the dynamic part added in v4.
True in Java you have to explicitly declare everything but that's not
what you wrote. Thinking about what an object is capable of is
required in both dynamically and statically typed languages after all
it's not magically type and a sensible implementation of a method
named Foo doesn't appear on any given object in Ruby just because you
try to invoke :)

But I guess I'm way of topic again kick an old can down the road:)

Ant Kutschera

unread,
Sep 13, 2011, 7:07:55 PM9/13/11
to object-composition
On Sep 13, 11:34 pm, rune funch <funchsolt...@gmail.com> wrote:
> But I guess I'm way of topic again kick an old can down the road:)

Never mind, it made me read what James wrote again:

On Sep 13, 3:37 am, "James O. Coplien" <jcopl...@gmail.com> wrote:
> > In a way, that is like asking Big Momma to recite lines from Big
> > Poppa's script, just because they are both being played by Eddie
> > Murphy... This is one of the reason's I really don't like dynamic
> > languages, and why I don't like them for DCI, because it is so easy to
> > confused what the object really is, and no IDE can help you figure it
> > out, unless you use the debugger.
>
> I think this comment makes it clear that you are thinking too much about the classness of a particular object, as if you are nervous about an object being versatile at run-time.

So, am I nervous about an object being versatile at run-time?

No, not if it's name is suitable. But James says above, "It's just a
name, not an object. That's what roles are." I.e. names are what
roles are. That tells me, the name given to my object is (related to)
the role it is playing. When I call an object "fireman" it is playing
the role "Fireman".

James then says "One should use the type system to communicate design
information."

So I should also give that object the type "Fireman", if my language
forces me to declare its type.

Or maybe that is the problem? Maybe roles don't have types? Maybe
the code needs to read like this:

Human me = ...
Human fireman = me assign Fireman;

I.e., there is no *type* Fireman. On the left, I used "Human" on both
lines.

But can the compiler then tell me that I have coded something wrong,
if I for example type a mistake like this:

fireman.putOopsSpellingMistakeOwtFire();

If it can, great! But I don't think the compiler can tell me.
Consider this code:

Human me = ...
doSomethingDangerous(me);

Human fireman = me assign Fireman;
doSomethingDangerous(fireman);

void doSomethingDangerous(Human h){
h.putOutFire(); //method from Fireman role
}

Without giving roles a type, I cannot get the compiler to tell me that
the first method call will fail.

So, that kind of proves to me, that for languages that require type
declarations, roles need their own type. With that statement, let's
now look at James' code:

module Neighbor
include ContextAccessor

def relable_node_as(x)
if x < self.tentative_distance;
self.set_tentative_distance_to(x); return true
else return false end
end
end

"self" is the object playing the role, right? So it is a Node playing
the role of Neighbor. I just declared that in languages which force
type declaration, roles must have types. So while the role player
"self" is a Node, it is also of type Neighbor.

So I think it is wrong for the code block above to call
"set_tentative_distance_to" which is a role method from the role
Distance_Labeled_Graph_Node.

Or let's look at a different example, where James used a name for his
object, rather than "self":

def nearest_unvisited_node_to_target
min = infinity
selection = nil
unvisited.each_key { |intersection|
if unvisited[intersection]
*** if intersection.tentative_distance < min
min = intersection.tentative_distance
selection = intersection
end
end
}
return selection
end

Here, I haven't yet decided if I think it is OK to do what he has done
on the line marked "***". But he reads the *field*
"tentative_distance" off the "intersection", which is actually a
temporary field on the object, i.e. on the Node. But adding fields to
objects is a totally different discussion anyway.

So imagine he had written the following line of code instead of the
one marked "***":

min = intersection.get_tentative_distance

See the "get"? That's the important part. It means we are imagining
he called a role method called "get_tentative_distance". I claim he
can't legally do that, because the name of the object is
"intersection", implying it's type is "Intersection".
"get_tentative_distance" is not a method of that role.

The alternative is to create a merged-interface, but James claims that
is wrong, because it leaves the reader unable to reason about the
intented use of the object - I disagree, because the name gives me
that ability to reason.

James says I am thinking too class oriented and not enough object
oriented. If that is true, it is because my language is forcing me to
declare types. If that is bad, then maybe, a requirement for DCI
certification is that the language must not force programmers to
declare types? I hope it isn't - I like type declarations because
they let the compiler and IDE help me a lot.

James O. Coplien

unread,
Sep 13, 2011, 9:32:29 PM9/13/11
to object-co...@googlegroups.com

On Sep 13, 2011, at 4:07 , Ant Kutschera wrote:

> James says I am thinking too class oriented and not enough object
> oriented. If that is true, it is because my language is forcing me to
> declare types.

No, it's because your language's type system puts the abstraction boundaries in the wrong place, and that gets in the way of separating concerns in a way that facilitates thinking about objects.


> If that is bad, then maybe, a requirement for DCI
> certification is that the language must not force programmers to
> declare types?

It works just fine in C++.

Simon Harris

unread,
Sep 13, 2011, 9:58:43 PM9/13/11
to object-co...@googlegroups.com
On Sep 13, 2011, at 4:07 , Ant Kutschera wrote:

> DCI certification

*shudder*

James O. Coplien

unread,
Sep 13, 2011, 10:22:30 PM9/13/11
to object-co...@googlegroups.com

On Sep 13, 2011, at 1:27 , Ant Kutschera wrote:

> So I am not sure how my code loses the design information that
> "current" represents the current intersection, other than it has the
> type "Mega".

That is exactly my point.

It's a Java problem: my C++ code doesn't suffer from this problem.

James O. Coplien

unread,
Sep 13, 2011, 10:24:22 PM9/13/11
to object-co...@googlegroups.com

On Sep 13, 2011, at 1:27 , Ant Kutschera wrote:

> But I guess there are still a lot of unanswered
> questions lying around, for example, the best way to make behaviour
> usable in more than one context.


I think it's been answered both here on the list and in the Lean Arch book, where habits are presented as one solution.

James O. Coplien

unread,
Sep 14, 2011, 1:17:30 AM9/14/11
to object-co...@googlegroups.com
This discussion seems to be stuck on pro-forma justification. I could answer it in a pro-forma way, and say that Ruby does not require advance declaration of data members. One can imagine that the data class always had the requisite member, and that the role simply elicits the tacit member by reference.

But to ask to go back to basics, and to explore the rationale behind the taboo against role data members was not a rhetorical question. I haven't seen any discussion here about the code that is based on anything other than an unrationalized fear of data members in roles. How, exactly, do you fear this code will break? I know an answer and think I know what Trygve and I understand to be the principles here, but I'm curious if they're understood.

I don't want DCI to turn into a cargo cult phenomenon, and I'm already a bit worried about that seeing the attempts in Java and with wrappers.

On Sep 13, 2011, at 8:40 , Stephan Herrmann wrote:

James O. Coplien

unread,
Sep 14, 2011, 1:20:07 AM9/14/11
to object-co...@googlegroups.com

On Sep 13, 2011, at 2:24 , Ant Kutschera wrote:

> Perhaps I mean *strongly* typed?


Smalltalk is much more strongly typed than C++. There is much less ability to cast or to let an object or one type masquerade as another.

Perhaps you meant: Strongly hyped languages? :-)

Simon Harris

unread,
Sep 14, 2011, 1:38:53 AM9/14/11
to object-co...@googlegroups.com, object-co...@googlegroups.com

vs duct taped languages :)

Andrzej Krzywda

unread,
Sep 14, 2011, 8:37:11 AM9/14/11
to object-co...@googlegroups.com

But to ask to go back to basics, and to explore the rationale behind the taboo against role data members was not a rhetorical question. I haven't seen any discussion here about the code that is based on anything other than an unrationalized fear of data members in roles. How, exactly, do you fear this code will break?  I know an answer and think I know what Trygve and I understand to be the principles here, but I'm curious if they're understood.

I don't want DCI to turn into a cargo cult phenomenon, and I'm already a bit worried about that seeing the attempts in Java and with wrappers.



Could you elaborate on the topic of roles containing data members?

We have this discussion in our project where we use DCI quite heavily (with Ruby). I come from the AOP background and there was a similar discussion between symmetrical (hyperj) and asymmetrical (aspectj) AOP. My current thinking is for having data members in roles, as the methods and data members fit together very well. One argument is reusability - it's easier to reuse a role when it contains most of the data members it needs.

I'd love to know your opinion on it.

With regards,
Andrzej Krzywda
It is loading more messages.
0 new messages