Submitted by: Alex Solla
This revision by: Alex Solla
Date: Wed Jun 27 01:01:39 -0400 2007
If a class mixes in a module implementing a particular API, then another implementing the same API, it cannot recover the first implementation by including the first module. A solution to this problem would be to modify rb_mod_append_features
function in Obj.c
to append the methods in the inheritance chain even if they already exist.
When writing a program utilizing a client-server architecture, one often wants to write server objects that respond to different “kinds” of client requests in a different manner, while still maintaining a stable API the client can use. Using mixins to solve this problem seems very natural. Consider the following code:
class Request
attr_reader :kind, :data
def initialize(kind, data)
@kind = kind
@data = data
end
end
module Identity
def process(data)
return data
end
end
module Double
def process(data)
return data + data
end
end
class Service
def initialize # Unimportant details here.
end
def run(request)
self.class.class_eval("include #{request.kind}")
process(request.data)
end
end
service = Service.new()
request = Request.new("Identity", 1)
request2 = Request.new("Double", 1)
request3 = Request.new("Identity", 1)
service.run(request) # returns 1
service.run(request2) # returns 2
service.run(request3) # returns 2 instead of the intended 1.
—End of Code
As the last comment notes, service.run(request3)
returns 2
instead of the intended 1
. That is to say, this pattern cannot be implemented using the current semantics for Module.include
.
This proposal does not affect Ruby’s syntax. The proposal is to re-implement Module::include so that the last included module in a class is appended to the inheritance chain, even if it has been included in the class previously. This will only affect Ruby code that relies on the behavior that a module can override another module’s methods, and spuriously attempts to load the original module.
class Service
def run(request)
self.instance_eval("#{request.kind}::process(#{request.data})")
end
end
Indeed, we are no longer even using mixins. The semantics of the instance_eval
method and variable interpolation make this a confusing hack. This would also require running instance_eval
on every invocation of run, since the complete method named invoked by run
must be passed to a different context each time. If my suggestion is implemented, the original class Service
can be modified to
class Service
def initialize # Unimportant details here.
@last = ""
end
def run(request)
unless @last == request.kind
self.class.class_eval("include #{request.kind}")
@last = request.kind
end
process(request.data)
end
end
effictively caching service
’s role. Depending on how often service
’s role changes, this can be significantly faster than the version using instance_eval
. On the other hand, if service
’s role changes frequently, the modified version would likely be much slower. because of the overheard of virtual class creation.
I have experimented with Object::Send
, but was unable to construct a run
method that worked as intended.
Several other thoughts have occured to me. If my suggesting is implemented, duplicate Modules names should likely be removed the inheritance chain. Otherwise, the chain can grow arbitrarily long during a service
objects lifetime. Indeed, this can occur even if they are removed, should an arbitrarily long list of roles be demanded of the service
. But that would be poor design, not something we should really concern ourselves about. However, a mechanism to explicitly remove a module from a class’s inheritance chain can be implemented as well.
Mixins that vary through time seem like a natural extension of the mixin functionality. However, perhaps a new Module instance method could be implemented to automate module loading. implemented_by :symbol
would be my suggestion.
It is my understanding that this proposal would require changes to Ruby’s interpreter. I am not good with C, so I shall leave this to the professionals.
2007/6/27, rcrchive+13@rcrchive.net <rcrchive+13@rcrchive.net>: > This is a digest of all the comments, so far, on RCR 13, > "Multiple Module Inclusion". You'll be on the mailing list for all future > comments. You can also add a comment by replying to this digest, > but please trim it down! > > > On Wed Jun 27 06:41:42 EDT 2007, Trans Onoma said: > > I concur with the over all effort of this RCR. > > However, I think a more complete solution should be had, in which the > programmer can remove mixins or swap one mixin for another, and > perhaps even control the mixin chain as one can manipulate an array. > > T. > > --------------------------------------------- The sample code does not convince me of the usefulness of this suggestion. There are other solutions than the one noted, for example using a Delegator per invocation which will extend with the proper module. Generally I consider it a bad idea to continuously change the inheritance hierarchy of a class at runtime - especially since there seems to be a design flaw here: method Service#run is an instance method and so if at all the type of the particular instance executing the method should change, not the type of all the instances of this class. But that is exactly what happens with the class_eval. Being able to manipulate the inheritance chain with meta programming (as T. mentions) is different story. That's probably useful although I do not have a use case for this yet. I suggest to open another RCR for this if that use case can be found. robert
On 6/27/07, rcrchive+13@rcrchive.net <rcrchive+13@rcrchive.net> wrote: > Welcome to the comment list for: > > Multiple Module Inclusion (RCR #13) > > This RCR was submitted by Alex Solla, > at Wed Jun 27 01:01:39 EDT 2007. > > > To comment on this RCR, just reply to this message, using the Reply-To > address. All commenting will be done via this email list. > > Please trim away this introductory message when you reply! > > > This is a digest of all the comments, so far, on RCR 13, > "Multiple Module Inclusion". You'll be on the mailing list for all future > comments. You can also add a comment by replying to this digest, > but please trim it down! > > > On Wed Jun 27 06:41:42 EDT 2007, Trans Onoma said: > > I concur with the over all effort of this RCR. > > However, I think a more complete solution should be had, in which the > programmer can remove mixins or swap one mixin for another, and > perhaps even control the mixin chain as one can manipulate an array. > > T. > > > --------------------------------------------- > > On Wed Jun 27 07:16:26 EDT 2007, Robert Klemme said: > > 2007/6/27, rcrchive+13@rcrchive.net <rcrchive+13@rcrchive.net>: > > This is a digest of all the comments, so far, on RCR 13, > > "Multiple Module Inclusion". You'll be on the mailing list for all future > > comments. You can also add a comment by replying to this digest, > > but please trim it down! > > > > > > On Wed Jun 27 06:41:42 EDT 2007, Trans Onoma said: > > > > I concur with the over all effort of this RCR. > > > > However, I think a more complete solution should be had, in which the > > programmer can remove mixins or swap one mixin for another, and > > perhaps even control the mixin chain as one can manipulate an array. > > > > T. > > > > --------------------------------------------- > > The sample code does not convince me of the usefulness of this > suggestion. There are other solutions than the one noted, for example > using a Delegator per invocation which will extend with the proper > module. Generally I consider it a bad idea to continuously change the > inheritance hierarchy of a class at runtime - especially since there > seems to be a design flaw here: method Service#run is an instance > method and so if at all the type of the particular instance executing > the method should change, not the type of all the instances of this > class. But that is exactly what happens with the class_eval. > > Being able to manipulate the inheritance chain with meta programming > (as T. mentions) is different story. That's probably useful although > I do not have a use case for this yet. I suggest to open another RCR > for this if that use case can be found. > > robert > > > --------------------------------------------- I am rather going with Robert on this, not that I think that this is nonsense, far from that, but I feel that the base of this RCR is not right. Module inclusion does just not seem the right tool for what Alex wants to achieve. Furthermore it would be nice if the standard procedure for RCRs were followed, i.e. discuss your idea on the mailing list before. I have refrained from making two RCRs thanks to the opinions of learned members of that list. I will change my vote if discussing this topic brings new insights, but I feel that such a discussion is an absolute must. Cheers Robert
Copyright © 2006, Ruby Power and Light, LLC
Comments
from Trans Onoma, Wed Jun 27 06:41:42 -0400 2007