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 K… 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 K. 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