From: poopdeville@... Date: 2007-06-28T04:50:10+09:00 Subject: RCR 13 Hi everybody, In case you've been following the RCR's, I wrote RCR 13 the other day. I intend to withdraw it for now, but I would still like to request comments from the list/usenet group. Here's the text of the RCR -- please note that I made a small change to the text in response to an objection by Robert Klemme. While his objection was worthwhile, it can be fixed with a single line of intuitive code. ABSTRACT 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. PROBLEM 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 # implementation specific. end def run(request) self.instance_eval("extend #{request.kind}") # different in RCR. 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. PROPOSAL 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. ANALYSIS A different solution for this problem already exists, but it is not as obvious or elegant. Instead of the class Service definition in the Problem section, we can write 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.instance_eval("extend #{request.kind}") # different in RCR. @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. IMPLEMENTATION 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. --END OF RCR Robert Klemme suggested that my original design in the code was flawed, since I presumably want different Service objects fullfilling different roles at run-time, and my implementation of including mixins at run-time was class-wide. I modified the code so that each Service object is extended by a module. Since obj.extend(module) is equivalent to class << obj; include module; end, the problem raise in the RCR still stands. Robert also noted that the problem can be avoided completely using the delegator design pattern. I would suggest that because of Ruby's use of virtual classes when including a module, my design is an elegant reformulation of the delegator pattern. Trans suggested that a more general solution to my problem exists: Exposing the inheritance chain as an array. I would have no problem with this, as it would allow me to implement my hypothetical implemented_by method. I would be happy to write an RCR based on this suggestion. So I welcome all your comments regarding RCR 13 and Trans's suggestion. Other use cases for my suggestion or Trans's suggestion would be especially appreciated. Thanks! Alex