DCI in Ruby

157 views
Skip to first unread message

James O Coplien

unread,
Oct 25, 2014, 9:06:18 PM10/25/14
to object-co...@googlegroups.com, Jim Gay
Hej, everyone,

Ruby has become one of the most popular languages of choice to implement DCI designs, and I have long wanted to polish off some of the small remaining mismatches between Ruby and our DCI ideals. After two years of planning and anticipation, yesterday I was finally able to meet with Yukihiro Matsumoto, the inventor and primary thought leader of Ruby, to bring our case to him.

Matz was a patient listener and he grasped DCI quickly. He came around to suggest that refinement might solve our problem:

module MoneyTransfer
module MoneySource
end
module MoneySink
end
refine MoneySource do
....
end
refine MoneySink do
....
end
end

I recall we explored this before but was embarrassed not to remember why we abandoned it. Can anyone remember?

He suggested an alternative syntax that he could add to Ruby that would enable us to do exactly the same just-in-time role method binding as in the Squeak example:

object.role-name.method arg arg

Please give me your input to help me take this further with Matz. Thanks!

— Cope

rune funch

unread,
Oct 26, 2014, 5:06:24 AM10/26/14
to object-co...@googlegroups.com
I think the main issue with The build in ruby features we've discussed is that role methods are leaked ie they are available even when the object is not playing that particular role either because the context has ended it's lifespan or because we're in another context where the same object plays a role. Don't know if this is true for refinement based approaches. The second objection I've heard was speed but that was specific to extend based solutions (Those two objections were the main reason why I created the maroon gem). The down side of that is that you move to a more static view of objects than what you generally have in Ruby

-Rune

--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To post to this group, send email to object-co...@googlegroups.com.
Visit this group at http://groups.google.com/group/object-composition.
For more options, visit https://groups.google.com/d/optout.

James O Coplien

unread,
Oct 26, 2014, 7:32:34 AM10/26/14
to object-co...@googlegroups.com
On 26 Oct 2014, at 18:06, rune funch <funchs...@gmail.com> wrote:

I think the main issue with The build in ruby features we've discussed is that role methods are leaked ie they are available even when the object is not playing that particular role either because the context has ended it's lifespan or because we're in another context where the same object plays a role. Don't know if this is true for refinement based approaches.

As Matz described it, refinement fixes both of these problems.

The second objection I've heard was speed but that was specific to extend based solutions (Those two objections were the main reason why I created the maroon gem). The down side of that is that you move to a more static view of objects than what you generally have in Ruby

I am ignoring the speed arguments for now, because Ruby is always evolving in the performance direction. If this becomes a high-runner feature in Ruby they'll be attentive to making it work faster.

They are hungry for real-world examples to support the use of refinement so they can make it work in the best way possible to meet a real need.

Matthew Browne

unread,
Oct 26, 2014, 8:47:13 AM10/26/14
to object-co...@googlegroups.com
On Sunday, October 26, 2014 7:32:34 AM UTC-4, Cope wrote:
As Matz described it, refinement fixes both of these problems.

If this means that a correct DCI implementation (or very close) is possible in native Ruby, that brings up the question of what the official Ruby DCI examples should be written in. Currently, the example on fulloo.info is written in native Ruby. Would it be better to update it to use Ruby's refinement feature, or to post a version using Maroon so the syntax is more ideal for DCI (along with an explanation that Maroon is required)? I think perhaps the best thing would be to post both versions...

James O Coplien

unread,
Oct 26, 2014, 9:18:23 AM10/26/14
to object-co...@googlegroups.com

On 26 Oct 2014, at 21:47, Matthew Browne <mbro...@gmail.com> wrote:

If this means that a correct DCI implementation (or very close) is possible in native Ruby, that brings up the question of what the official Ruby DCI examples should be written in. Currently, the example on fulloo.info is written in native Ruby. Would it be better to update it to use Ruby's refinement feature, or to post a version using Maroon so the syntax is more ideal for DCI (along with an explanation that Maroon is required)? I think perhaps the best thing would be to post both versions...

Yes, yes, yes. But before (prematurely) deciding to do something now based on the way Ruby is, we have a once-in-a-lifetime opportunity to influence the direction of Ruby to support DCI. It will not be a long window.

What should it look like?

Matthew Browne

unread,
Oct 26, 2014, 9:45:54 AM10/26/14
to object-co...@googlegroups.com
Yes, sorry...I just wanted to mention the example while it was on my mind, but that discussion can and probably should happen later.

Personally I always prefer syntax that's as close as possible to the actual intent, which is why I like implementations with a "role" keyword. The risk with introducing such a keyword to the core language, however, is that unless it's marketed specifically (and only) as a feature for doing DCI, it would be used for other things as well, and could cause confusion about DCI and water down the meaning of the word "role".

As to the "refine" syntax, it looks like you would have to define role methods and bind them to the role in a single step. I think those should be two separate operations, especially if we want to allow roles to be re-bound as Trygve suggests (and which I was surprised to learn Marvin doesn't support, although Rune provided a good explanation for why).

Using the refinement feature, how would you assign a variable to a role with a different name than the variable? A slightly more complete example might be helpful...
--
You received this message because you are subscribed to a topic in the Google Groups "object-composition" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/object-composition/AsvEI7iJSDs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to object-composit...@googlegroups.com.

rune funch

unread,
Oct 26, 2014, 2:30:27 PM10/26/14
to object-co...@googlegroups.com
Though I would like a role and a context keyword in Ruby I'm kind of with Matt that they might dilute the understading of DCI because they could be used contrary to the DCI mindset. For that in a language not specifically made for DCI I think I'd prefer something general. refine might be just that. However as I read the code you define a type which you then refine in the scope of the context. I'd love to see the refinement on an object basis. Something like:

module MoneyTransfer
@MoneySource, @MoneySink

refine MoneySource do
....
end
refine MoneySink do
....
end
end

--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.

rune funch

unread,
Oct 26, 2014, 2:32:31 PM10/26/14
to object-co...@googlegroups.com

2014-10-26 19:30 GMT+01:00 rune funch <funchs...@gmail.com>:
module MoneyTransfer
@MoneySource, @MoneySink

refine MoneySource do
....
end
refine MoneySink do
....
end
end
With complete code, same "quotation level"

module MoneyTransfer
    @MoneySource, @MoneySink
    refine @MoneySource do
    end
    refine @MoneySink do
    end
end

Jim Gay

unread,
Oct 27, 2014, 10:55:54 AM10/27/14
to object-co...@googlegroups.com
The issues with refinements in the past, as I saw it, was that firstly it was an experimental feature and wasn't a part of standard Ruby until the 2.1 release. You could use it, but most developers would not compile Ruby with the proper settings to enable it.

The other issue with both Ruby and refinements is that the objects never lose their roles.

While you can contextually alter the features of a module in Ruby using the refine keyword, if you extend an object like this:

role_player.extend(RoleModule)

... that role_player object never loses its role. It will forever (until the object goes through garbage collection) return true if you ask if role_player.is_a?(RoleModule)

This presents the problem that a user object could accidentally be marked as an Employee and Manager in the same context where it should only be one or the other.

Ruby has no "unextend" behavior to remove the role from the ancestry.

If the original example code would be used to get around this, I don't understand how.

If Matz is agreeable to adding something like:

object.role-name.method arg arg

to the parser, that would be fantastic. I'm not exactly clear how that "role-name" part would reconcile with the related module but if there is some contextual method lookup then that pretty much solves Ruby's problem with DCI.

Have I misunderstood any part of this?

-Jim

--
Write intention revealing code #=> http://www.clean-ruby.com
Jim Gay
Saturn Flyer LLC
571-403-0338

Jim Gay

unread,
Oct 27, 2014, 11:48:41 AM10/27/14
to object-co...@googlegroups.com
On Oct 27, 2014, at 10:55 , Jim Gay <j...@saturnflyer.com> wrote:

The issues with refinements in the past, as I saw it, was that firstly it was an experimental feature and wasn't a part of standard Ruby until the 2.1 release. You could use it, but most developers would not compile Ruby with the proper settings to enable it.

The other issue with both Ruby and refinements is that the objects never lose their roles.

While you can contextually alter the features of a module in Ruby using the refine keyword, if you extend an object like this:

role_player.extend(RoleModule)

... that role_player object never loses its role. It will forever (until the object goes through garbage collection) return true if you ask if role_player.is_a?(RoleModule)

This presents the problem that a user object could accidentally be marked as an Employee and Manager in the same context where it should only be one or the other.

Ruby has no "unextend" behavior to remove the role from the ancestry.

If the original example code would be used to get around this, I don't understand how.

If Matz is agreeable to adding something like:

object.role-name.method arg arg

to the parser, that would be fantastic. I'm not exactly clear how that "role-name" part would reconcile with the related module but if there is some contextual method lookup then that pretty much solves Ruby's problem with DCI.

Have I misunderstood any part of this?

The other issue with refinements is that the "refine" keyword works only on classes.

In order for a User instance to act as an Employee or Manager, it would need to be initialized as an Employee class instance and a Manager class instance.
If you attempt to refine a module you get:

`refine': wrong argument type Module (expected Class) (TypeError)

One could conceivably refine the singleton_class of an instance of a class, but in order to do that in context, we'd need to use a method apply the refinement and I've not found a way that Ruby allows either the "refine" or "using" keywords inside a method.

I had played with this in the past and wondered if something new came along, but as far as I can tell Ruby's refinements puts us squarely in class-oriented design.

Trygve Reenskaug

unread,
Oct 27, 2014, 12:18:14 PM10/27/14
to James O. Coplien, object-co...@googlegroups.com
AFAICR, the main problem with Ruby and other languages is not to add functionality just in time, but to get rid of it when it's no longer needed.

Also, in BabyIDE an object is forced to execute a role method without it ever becoming part of the object's class. This avoids any name clashes with the object's local behavior and the behavior of any other  roles the object may be mapped to at the same time.

The latest ARTICLE draft:
"An object's behavior is is composed from its local behavior together with the behavior of the role it happens to be playing at any given time."
— Copemailto: try...@ifi.uio.no
Morgedalsvn. 5A       http://folk.uio.no/trygver/
N-0378 Oslo             http://fullOO.info
Norway                     Tel: (+47) 22 49 57 27

Jim Gay

unread,
Oct 27, 2014, 12:30:47 PM10/27/14
to object-co...@googlegroups.com, James O. Coplien
On Oct 27, 2014, at 12:18 , Trygve Reenskaug <try...@ifi.uio.no> wrote:

AFAICR, the main problem with Ruby and other languages is not to add functionality just in time, but to get rid of it when it's no longer needed.

Yes. Absolutely.

I wrote a library called "casting" which piggybacks on Ruby's ability to bind methods to any object and when in use looks like this:

role_player.delegate(:role_method, RoleModule)

Which will execute the role_method but RoleModule will never be added to the object.
Optionally you can tie it to method missing so that all you'd need to do is:

role_player.role_method

And it would search through a collection of delegates which contain the missing method.

The problem with this is that "super" doesn't work, so it loses the ability to alter existing behavior. I've reported this (super not working in this scenario) as a bug and if Matz would consider it, it would push things in the right direction, although greater support for contextual method lookup would be even better.


Risto Välimäki

unread,
Oct 27, 2014, 12:44:23 PM10/27/14
to object-co...@googlegroups.com, James O. Coplien
2014-10-27 18:18 GMT+02:00 Trygve Reenskaug <try...@ifi.uio.no>:
AFAICR, the main problem with Ruby and other languages is not to add functionality just in time, but to get rid of it when it's no longer needed.

Also, in BabyIDE an object is forced to execute a role method without it ever becoming part of the object's class. This avoids any name clashes with the object's local behavior and the behavior of any other  roles the object may be mapped to at the same time.

+1

And while the "getting rid of" -problem is more obvious, the name clashes problem is often overlooked, but even more serious.

It's not only about "JIT" Roles, but also "JIRC", Just-In-the-Right-Context. Or maybe you could even say that it's not at all about "JIT", but only about "JIRC". It's just scary what might happen if a role method was leaked into other context or worse: into the very basic functions of the role player object.

--

Trygve Reenskaug

unread,
Oct 27, 2014, 12:58:38 PM10/27/14
to object-co...@googlegroups.com, James O. Coplien
Two essential  essential Squeak featuresenable the BabyIDE implementation:
  1. Squeak methods are free-floating. They are essentially collections of byte codes. They are not linked to any class.
    Traits was an example of free-floating methods.
  2. An object can be made to execute a method, thereby defining the meaning of 'self'.
    Class Object (the super-superclass of all classes)  defines this method:
            withArgs: argArray executeMethod: compiledMethod
So all I needed to do was to keep the role methods that belong to a particular role somewhere, recognize that a message is sent to a role, identify the role method (if any), and send the message withArgs:exdecuteMethod: to the current role player object. If there is no role method, then I send the message to the object in the normal way:
        compiledMethod
            ifNil: [receiver perform: selector withArguments: argCollection asArray]
            ifNotNil: [receiver withArgs: argCollection asArray executeMethod: compiledMethod]
All it took was a slight modification of the compiler.

The above two essential features are all that need to be added to the Ruby core.  Anybody with access to the compiler code can then do the rest.




On 26.10.2014 02:06, James O Coplien wrote:
--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To post to this group, send email to object-co...@googlegroups.com.
Visit this group at http://groups.google.com/group/object-composition.
For more options, visit https://groups.google.com/d/optout.

--

Trygve Reenskaug      mailto: try...@ifi.uio.no

James O. Coplien

unread,
Oct 27, 2014, 6:41:31 PM10/27/14
to object-co...@googlegroups.com

On 28 Oct 2014, at 01:30, Jim Gay <j...@saturnflyer.com> wrote:

AFAICR, the main problem with Ruby and other languages is not to add functionality just in time, but to get rid of it when it's no longer needed.

I did not bring this up with Matz because I cannot think of a scenario based on sticky injection that causes problems if refinement is used. Do you have one?

Trygve Reenskaug

unread,
Oct 28, 2014, 5:24:14 AM10/28/14
to James O. Coplien, object-co...@googlegroups.com
Not sure what you mean by 'refinement'.   Please explain.
-----------------------
I suggest you look at my Dijkstra/BabyIDE example.

The program has 4 methodless roles: CONTEXT, MAP, UNVISITED, and DISTANCEDICT
and  3 methodful roles: CURRENTINTERSECTION, EASTNEIGHBOR, SOUTHNEIGHBOR.

CURRENTINTERSECTION>>computeDistanceToNeighbors
    " This method computes the final distance for the node named CURRENTINTERSECTION. "
    EASTNEIGHBOR  ifNotNil:
        [EASTNEIGHBOR recomputeTentativeDistance].
    SOUTHNEIGHBOR ifNotNil:
        [SOUTHNEIGHBOR recomputeTentativeDistance].
    UNVISITED remove: self.
    UNVISITED ifNotEmpty: [CONTEXT recur].

EASTNEIGHBOR>>recomputeTentativeDistance
    | oldTentDist newTentDist |
    Transcript cr; show: {CURRENTINTERSECTION name. '->'. self name.  ' ('. #EASTNEIGHBOR. ').'.}.
    oldTentDist := (DISTANCEDICT at: self) distance.
    newTentDist := (DISTANCEDICT at: CURRENTINTERSECTION) distance
                         + (CURRENTINTERSECTION distanceTo: self).
    newTentDist < oldTentDist
    ifTrue:
        [(DISTANCEDICT at: self) distance: newTentDist previousNode: CURRENTINTERSECTION].

SOUTHNEIGHBOR>>recomputeTentativeDistance
    | oldTentDist newTentDist |
    Transcript cr; show: {CURRENTINTERSECTION name. '->'. self name. ' ('. #SOUTHNEIGHBOR. ').'.}.
    oldTentDist := (DISTANCEDICT at: self) distance.
    newTentDist := (DISTANCEDICT at: CURRENTINTERSECTION) distance
                         + (CURRENTINTERSECTION distanceTo: self).
    newTentDist < oldTentDist
    ifTrue:
        [(DISTANCEDICT at: self) distance: newTentDist previousNode: CURRENTINTERSECTION].

All intersections are played by an instance of class Node.
All of them will be mapped to almost all roles at the bottom of the recursion.
EASTNEIGHBOR and SOUTHNEIGHBOR specify two different role methods with the same name: recomputeTentativeDistance.
(The difference is in the first line).

Cope: Did you have to choose the role method names carefully in  your fist C++ implementation?


Finally: Who are we to prohibit future programmers to give all three role methods the same name? (e.g., computeDistance)





Risto Välimäki

unread,
Oct 28, 2014, 6:13:15 AM10/28/14
to object-co...@googlegroups.com, James O. Coplien
Finally: Who are we to prohibit future programmers to give all three role methods the same name? (e.g., computeDistance)

This! And please do not forget the instance methods as well. Typically they might have different names than role methods, but surely name clashes will occur sometimes.


Lets say we have a class named A that can fulfill a role named R. Let's say we have following instance methods on class A (in a fictional DCI-supporting language):

class A 
  method foo() 
    self.bar() // intention is to call instance method bar()
  end

  method bar() 
    ...
  end
end

...and role R (inside context C) has following methods:

context C 
  role R 
    method bar() 
      ... // Role method that should never instantiated from outside the context C
    end

    method x() 
      self.foo()  // calls the instance method foo()
    end
  end
end


I'd say it's a catastrophe, if calling role method x() would eventually result as calling instance method foo() and then role method bar() that was clearly not the intention of developer of the class A.

Instead, calling role method x() should result as calling instance method foo() that will call also instance method bar() (and NOT the role method .bar()!)


Typically, you get the correct behavior "free" with injectionless DCI implementations, due to the non-polluting method "injection" nature of these implementations.


Trygve, do you have any comments on this particular concern of mine?

-Risto

Egon Elbre

unread,
Oct 28, 2014, 6:57:13 AM10/28/14
to object-co...@googlegroups.com, co...@gertrudandcope.com
My main torture test is the following: 

It's a bad code example, but does the torturing job pretty well.

i.e. 
* objects (without classes) as role players
* accessing role player properties
* multiple contexts
* method name clashes inside a context
* role name clashes between different contexts
* multiple roles for a role player
* calling method on role player when passed into a function outside of context
* callbacks into context role methods
* at the end of the scenario, the role players should be the same 
    (given object player before and after the Tournament call, it should be impossible to tell the difference between them)

(probably, should add nested contexts and identity tests to that mix; but they can independently tested)

Anyways, IMHO, if it can pass all of that, without memory leaks, then it has handled all the corner cases properly.

+ egon

Marc Grue

unread,
Oct 28, 2014, 9:08:20 AM10/28/14
to object-co...@googlegroups.com, co...@gertrudandcope.com
On Tuesday, October 28, 2014 11:57:13 AM UTC+1, Egon Elbre wrote:

Implemented your torture test in Scala:

Side note: since `this` is a reserved keyword in Scala that would refer to the Context, I have chosen to disallow it as a RolePlayer reference to avoid colliding with Scala semantics and thereby confusing Scala people. Instead we can use `self`.

Thanks,
Marc

Trygve Reenskaug

unread,
Oct 28, 2014, 9:54:48 AM10/28/14
to object-co...@googlegroups.com

On 28.10.2014 11:57, Egon Elbre wrote:
My main torture test is the following: 

It's a bad code example, but does the torturing job pretty well.

i.e. 
* objects (without classes) as role players
Should be OK

* accessing role player properties
Should be OK if it is through messages in the RoleObjectContract.
* multiple contexts
Should be OK
* method name clashes inside a context
Should be OK
* role name clashes between different contexts
Should be OK
* multiple roles for a role player
Should be OK
* calling method on role player when passed into a function outside of context
I don't think I understand this one.
A role method can call a method in the role player. This method can, in principle, do anything.
But the DCI separation of concerns limit the useful 'anything'. Intuitively, I think that it should not access other role-players 'behind the scenes'. This point needs more work on it.
Roles and role players cannot be visible outside their context because role methods reference other roles in the context.
* callbacks into context role methods
Good point. I should like to see callbacks into the context object, and I believe this should be possible.  I can't see callbacks into roles via role methods. An object cannot uniquely identify which role it is playing at a given point in time.  (if there are moe than one candidate)

* at the end of the scenario, the role players should be the same 
    (given object player before and after the Tournament call, it should be impossible to tell the difference between them)
??? Both role-object mapping and the state of the role-playing objects may have changed
--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To post to this group, send email to object-co...@googlegroups.com.
Visit this group at http://groups.google.com/group/object-composition.
For more options, visit https://groups.google.com/d/optout.

--

Trygve Reenskaug      mailto: try...@ifi.uio.no

Egon Elbre

unread,
Oct 28, 2014, 10:24:55 AM10/28/14
to object-co...@googlegroups.com


On Tuesday, 28 October 2014 15:54:48 UTC+2, trygve wrote:

On 28.10.2014 11:57, Egon Elbre wrote:
My main torture test is the following: 

It's a bad code example, but does the torturing job pretty well.

i.e. 
* objects (without classes) as role players
Should be OK
* accessing role player properties
Should be OK if it is through messages in the RoleObjectContract.
* multiple contexts
Should be OK
* method name clashes inside a context
Should be OK
* role name clashes between different contexts
Should be OK
* multiple roles for a role player
Should be OK
* calling method on role player when passed into a function outside of context
I don't think I understand this one.
A role method can call a method in the role player. This method can, in principle, do anything.
But the DCI separation of concerns limit the useful 'anything'. Intuitively, I think that it should not access other role-players 'behind the scenes'. This point needs more work on it.
Roles and role players cannot be visible outside their context because role methods reference other roles in the context.

I probably worded that badly, here's principally the code that tests it:

function interview(player) {
console.log(player.say());
}

var Battle = Context(function(player){
Lion = player;
interview(Lion);
}, {
Lion: {
say: function(){
console.log("[" + this.name + "] meow...");
}
}
});

When we attach a role method to the "player" named "say"... and we call a function that is outside the context (e.g. some other context, some utility function)  it shouldn't see the role methods. It would be very surprising behavior, if you could accidentally call role methods outside of the context.

So, it ensures that the Role Methods are only visible in their own Context.

* callbacks into context role methods
Good point. I should like to see callbacks into the context object, and I believe this should be possible.  I can't see callbacks into roles via role methods. An object cannot uniquely identify which role it is playing at a given point in time.  (if there are moe than one candidate)

I simply close over the role identifiers with the callback function, this means, I can use it either inside the context or inside role methods, e.g:

Lion : {
say: function() {
console.log(Id + " [" + this.name + "] " + "Meow.....");
},
fight: function() {
// invoking role method via callback
callback(function(){
Lion.say();
});
}
}

* at the end of the scenario, the role players should be the same 
    (given object player before and after the Tournament call, it should be impossible to tell the difference between them)
??? Both role-object mapping and the state of the role-playing objects may have changed

It is after the context has ended / destroyed. Basically it's about testing whether the context gets cleaned up properly. If it doesn't have this property then it is quite likely that it also has a memory leak.
Reply all
Reply to author
Forward
0 new messages