RCR 187: Advanced Method Visibility
Submitted by minam (Fri Jan 09 00:01:05 UTC 2004)
Abstract
Ruby currently only provides 'public', 'protected', and 'private' feature visibility. This is sufficient for simple programs, but there are many times that a more advanced visiblity mechanism is desired. Eiffel, for instance, provides a powerful mechanism that allows for all traditional visibility levels (private, public, protected) as well as more elaborate ones. This RCR proposes an extended syntax for supporting a superset of Ruby's current visibility functionality, inspired by Eiffel's approach to the problem.
Problem
Ruby has no true "private" visibility; descendants of a class still have access to all of their ancestors' internal state. The "private" visibility in Ruby is more properly a "protected" visibility.
Also, there are times when you want a particular set of methods of a class to be private, and yet be accessible to members of a specific class (or set of classes). This is similar to the "friend" accessibility as implemented in C++, but more robust and with finer control over the granularity of the access.
Consider the "State" design pattern, as described in the "Design Patterns" book by Erich Gamma, et. al. You have some class A that has some internal state. At some point in your program, you want to take a snapshot of A's internal state, so that you can later restore A to that state. The State design pattern suggests creating some class B that encapsulates the internal state of A. B would have a "private" interface that A would be able to query, but no other class, allowing A to set and retrieve its internal state via B. Currently, there is no mechanism for doing this easily in Ruby.
Proposal
I propose that Ruby adopt a visibility mechanism similar to that used by Eiffel. The Module class would gain a new method (called
feature
in this RCR for compatibility with Eiffel, but it may be called anything in practice, perhaps
allow
or
access
). This method would act much like the private and public methods act in current versions of Ruby, but would instead accept a list of class names. All subsequently declared methods of the class would then be accessible only to the declaring class and to any descendent class of any of the classes given in the list.
The current private
, protected
, and public
methods would remain, for backwards compatibility, since they are easily converted to the corresponding idioms in the new model.
Here is an example using the proposed syntax:
class Demo
feature :Demo # grants visibility only to Demo and its descendents,
# i.e., "protected" visibility
def method1
...
end
feature :Object # grants visibility to all classes, i.e., "public"
def method2
...
end
feature :NilClass # grants visibility to no classes except the declaring
# class, i.e. "private" visibility
def method3
...
end
feature :OtherClass, :YetAnotherClass
# "friend" visibility, granting access only to the declaring class and
# to the classes
def method4
...
end
end
The usage of :Object
and :NilClass
to represent "everything" and "nothing" (respectively) correspond to Eiffel's use of Any
and None
. Perhaps a clearer system would be to have :Any
and :None
simply be aliases for :Object
and :NilClass
. Perhaps there's an even clearer way to represent the "None" scenario.
Analysis
Limiting the visibility of features will limit your ability to access certain pieces of code from outside of the declaring class. This is actually a desirable feature. The implementor of a class may declare many methods that he does not intend anyone else to call. Such functions are of use only internally, and as such their signatures, names, and return values are all liable to change. In order to prevent curious clients from accessing those methods (and potentially causing their code to break when a new version of the class is released), the implementor can declare them to be private (which is currently possible in Ruby).
It is also desirable from a design point-of-view. The whole point of the "encapsulation" aspect of object-oriented design and programming is to hide certain parts of a class from outside clients (and sometimes even from descendents of the class itself). By guaranteeing that clients cannot access a given method (or set of methods), you can more easily guarantee the consistent behavior of your class. Also, private, internal methods can forego parameter checking and other "sanity checks", since the implementor does not need to worry about a client passing incorrect data to them.
Adding this feature does not mean that every implementor of a class must use it. By default, all methods of a class will be public, just as they currently are in Ruby. But it does give the ability to those implementors that want it to use a more robust encapsulation mechanism.
Ruby already supports feature visibility, via the "public" and "private" methods. By increasing the power of this feature, more consistent programs may be written, without sacrificing the current Ruby feature visibility mechanism.
Implementation
There is a proof-of-concept implementation of this in the RAA, called Access+ (>). It approximates this behavior using standard Ruby, setting a trace function to keep track of who the current caller is.
This proof-of-concept, however, is not sufficent, since it is both easily circumvented (all it does is rename the original method and make a "wrapper" method that enforces the visibility) and fragile (it uses the set_trace method to detect when the calling method changes). The language itself needs to know about and be able to enforce the visibility of various method features.

When I was in school, I read Meyer's "Object-oriented Software Construction", and was influenced a lot. And this particular feature (selective export) is the feature I felt *agaist* most, after the research.
I mean, the basic concept of selective export is good, but the particular design in Eiffel seems ugly, because its against the policy of extensibility: the service provider should not restrict its clients, otherwise it would restrict the future use of the service. So, I designed the selective exporting feature in publish-and-subscribe model instead in the past.
Let me explain.
If the provider specifies classes for features to export, classes not known to the provider will never be able to access the "private" features. The workaround would be exporting features to (possibly) empty classes, and clients inherit these empty classes to "buy" features.
But why not be more direct? So I defined set of features, which I named "profile", and clients use profiles to gain visibility to the class.
class Demo {
method_1 {
...;
}
method_2 {
...;
}
profile DemoProfile1 {
method_1;
}
profile DemoProfile2 {
method_1;
method_2;
}
}
class Client {
first(DemoProfile1 demo) {
demo.method_1; # valid
demo.method_2; # invalid
}
second(DemoProfile2 demo) {
demo.method_1; # valid
demo.method_2; # valid
}
}
d = Demo.new
d.method_1 # invalid; because default visibility is private
Client.new.first(d) # DemoProfile1 = Demo assignment is valid
I designed a statically typed object-oriented language in my graduation thesis, which had this selective export (the above example in that language). Although it has not been published to anybody, I still think my design is superior than Meyer's in this aspect.
Note that the design is based on static typing, so that it cannot be directly applied to Ruby.
I am not sure whether Ruby needs this level of visibility control, but if it does, it should be much better than Eiffel's selective export.
- matz.
Matz: thanks for your excellent comment. It gave me a lot to consider, and after a lot of thought I can see that your approach is much more flexible than Eiffel's. To that end, I am going to withdraw this RCR and submit another one, with a proposed implementation for your publish/subscribe approach to selective export.
Thanks for helping me to "see the light." :)
- Jamis
|
Strongly opposed |
0 |
Opposed |
2 |
Neutral |
0 |
In favor |
1 |
Strongly advocate |
2 |
|


RCRchive copyright © David Alan Black, 2003-2005.
Powered by .
When I was in school, I read Meyer's "Object-oriented Software Construction", and was influenced a lot. And this particular feature (selective export) is the feature I felt *agaist* most, after the research.
I mean, the basic concept of selective export is good, but the particular design in Eiffel seems ugly, because its against the policy of extensibility: the service provider should not restrict its clients, otherwise it would restrict the future use of the service. So, I designed the selective exporting feature in publish-and-subscribe model instead in the past.
Let me explain.
If the provider specifies classes for features to export, classes not known to the provider will never be able to access the "private" features. The workaround would be exporting features to (possibly) empty classes, and clients inherit these empty classes to "buy" features.
But why not be more direct? So I defined set of features, which I named "profile", and clients use profiles to gain visibility to the class.
I designed a statically typed object-oriented language in my graduation thesis, which had this selective export (the above example in that language). Although it has not been published to anybody, I still think my design is superior than Meyer's in this aspect.
Note that the design is based on static typing, so that it cannot be directly applied to Ruby.
I am not sure whether Ruby needs this level of visibility control, but if it does, it should be much better than Eiffel's selective export.
- matz.
Matz: thanks for your excellent comment. It gave me a lot to consider, and after a lot of thought I can see that your approach is much more flexible than Eiffel's. To that end, I am going to withdraw this RCR and submit another one, with a proposed implementation for your publish/subscribe approach to selective export.
Thanks for helping me to "see the light." :)
- Jamis