Private visibility should be removed from Ruby 2 [was: Caveats with #method_missing]

>* remove "protected" visibility completely

Nobody uses "protected" (0 uses in standard library - can anybody find some
code that actually uses it ?). Nobody understands it.

I understand it just fine and have used it before. Here's an example where it might be needed:

>> class Name < Struct.new(:first, :last)
>> def full
>> "#{first} #{last}"
>> end
>>
?> def last_first
>> "#{last}, #{first}"
>> end
>>
?> def sortable
>> [last, first]
>> end
>> protected :sortable
>>
?> def <=>(other)
>> sortable <=> other.sortable
>> end
>> end
=> nil
>> names = [ %w[James Gray],
?> %w[Dana Gray],
?> %w[Joe Fair] ].inject(Array.new) { |a, n| a + [Name.new(*n)] }
=> [#<struct Name first="James", last="Gray">, #<struct Name first="Dana", last="Gray">, #<struct Name first="Joe", last="Fair">]
>> # OK, because protected allows the call to other.sortable:
?> names.sort.map { |n| n.last_first }
=> ["Fair, Joe", "Gray, Dana", "Gray, James"]
>> # Not OK, because sortable is for internal use only:
?> names.first.sortable
NoMethodError: protected method `sortable' called for #<struct Name first="James", last="Gray">
         from (irb):28

James Edward Gray II

···

On Oct 3, 2006, at 7:20 AM, Tomasz Wegrzanowski wrote:
         from :0

Hi,

>* remove "protected" visibility completely

Nobody uses "protected" (0 uses in standard library - can anybody find some
code that actually uses it ?). Nobody understands it.

"protected" is like "friend" in C++. Currently a few programs use it,
but it does not mean it isn't useful.

How was it supposed to work ? Am I the only one with a problem here ?

This is a bug. I should have checked protected visibility based on
real "self" within instance_eval. It will be fixed soon.

And getting rid of it make metaprogramming easier.
One example: I have no idea how to make delegation by method_missing
that behaves correctly with public, protected and private methods.
Delegation would either have to ignore visibility (what is wrong -
see preexplanation again) or fail to forward some methods.

Removing visibility makes the issue much easier, I admit. But you may
agree that visibility has its own merit on the other hand. It's just
a trade off. It might mean the demand for the delegation along with
the original visibility.

I will be back with your other points later.

              matz.

···

In message "Re: Private visibility should be removed from Ruby 2 [was: Caveats with #method_missing]" on Tue, 3 Oct 2006 21:20:21 +0900, "Tomasz Wegrzanowski" <tomasz.wegrzanowski@gmail.com> writes:

>> >* remove "protected" visibility completely
>
> Nobody uses "protected" (0 uses in standard library - can anybody
> find some
> code that actually uses it ?). Nobody understands it.

I understand it just fine

Then could you explain, whether:

  a = Name.new('foo', 'bar')
  a.instance_eval { a.sortable } # correct or not ?
  Name.instance_eval { a.sortable } # correct or not ?

are supposed to work or not, and why ?

and have used it before. Here's an example where it might be needed:

[example omitted]

This use is pretty nifty, as it saves you a class check:
  raise ArgumentError.new("comparison of #{self.class} with
#{other.class} failed") unless oher.is_a? Name

Except that it breaks later.

Think how can you make an object that forwards all messages it gets to
a Name object,
and use it as if it was real Name. This is often very useful (in
debugging, logging,
lazy loading, remote execution, mock objects in unit testing etc.) and
works with
pretty much all objects.

But it won't work correctly with your Name class - forwarding (method_missing)
must either do public method call (send in 1.9), which fails as
sortable is not public,
or private method call (funcall in 1.9 or instance_eval+send in 1.8),
which removes the class check, and therefore changes behaviour of <=>.

So Name fails to behave like a nice Ruby class.
I don't see how such problems can be reasonably fixed.

To forward protected calls, we would need some way of getting caller
context, what sounds pretty bad, as caller context is not simply class of
caller's self, it can be some of its superclasses etc,
and we need to make a version of send that uses this context.

Then we would do :
  def method_missing(*args, &blk)
    @obj.send_with_context(caller_context, *args, &blk)
  end

I think neither accepting unability to delegate nor actually having contexts
like that is acceptable trade-off for the little use "protected" has.

···

On 10/3/06, James Edward Gray II <james@grayproductions.net> wrote:

On Oct 3, 2006, at 7:20 AM, Tomasz Wegrzanowski wrote:

--
Tomasz Wegrzanowski [ http://t-a-w.blogspot.com/ ]

>> >* remove "protected" visibility completely
>
> Nobody uses "protected" (0 uses in standard library - can anybody
> find some
> code that actually uses it ?). Nobody understands it.

I understand it just fine

Then could you explain, whether:

These are probably questions better asked of irb, but I'll try...

a = Name.new('foo', 'bar')
a.instance_eval { a.sortable } # correct or not ?

I have no idea what "correct" means here, but I can tell you that Ruby allows protected methods to be called function-style for the current object and method style for another object of the same class:

>> a.instance_eval { a.sortable } # correct or not ?
NoMethodError: protected method `sortable' called for #<struct Name first="foo", last="bar">
         from (irb):20
>> a.instance_eval { sortable }
=> ["bar", "foo"]
>> a.instance_eval { self.sortable }
NoMethodError: protected method `sortable' called for #<struct Name first="foo", last="bar">
         from (irb):23

You do seem to be able to use either style in method definitions for the current object though:

>> class Name < Struct.new(:first, :last)
>> def full
>> "#{first} #{last}"
>> end
>>
?> def last_first
>> "#{last}, #{first}"
>> end
>>
?> def sortable
>> [last, first]
>> end
>> protected :sortable
>>
?> def <=>(other)
>> self.sortable <=> other.sortable
>> end
>> end
=> nil
>> Name.new("James", "Gray") <=> Name.new("Dana", "Gray")
=> 1

Name.instance_eval { a.sortable } # correct or not ?

Well, it blows up and I consider that "correct", yes. You are inside the Name class here, not an instance of that class.

James Edward Gray II

···

On Oct 3, 2006, at 11:07 AM, Tomasz Wegrzanowski wrote:

On 10/3/06, James Edward Gray II <james@grayproductions.net> wrote:

On Oct 3, 2006, at 7:20 AM, Tomasz Wegrzanowski wrote:

         from :0

So why doesn't a.instance_eval { self.sortable } work ?
I stand by my statement that pretty much nobody understands "protected".

···

On 10/3/06, James Edward Gray II <james@grayproductions.net> wrote:

On Oct 3, 2006, at 11:07 AM, Tomasz Wegrzanowski wrote:
>> a.instance_eval { self.sortable }
NoMethodError: protected method `sortable' called for #<struct Name
first="foo", last="bar">
         from (irb):23
         from :0

--
Tomasz Wegrzanowski [ http://t-a-w.blogspot.com/ ]

Because that's a method call, not a function-style call.

James Edward Gray II

···

On Oct 3, 2006, at 11:38 AM, Tomasz Wegrzanowski wrote:

On 10/3/06, James Edward Gray II <james@grayproductions.net> wrote:

On Oct 3, 2006, at 11:07 AM, Tomasz Wegrzanowski wrote:
>> a.instance_eval { self.sortable }
NoMethodError: protected method `sortable' called for #<struct Name
first="foo", last="bar">
         from (irb):23
         from :0

So why doesn't a.instance_eval { self.sortable } work ?
I stand by my statement that pretty much nobody understands "protected".

So what's the final rule ?

Is it something like:
  protected method can be called if "implicit self" is used
  or if self is the same or sub class of class in which method
  was defined, and only from a method definition by `def'"

Is it correct ?
Why does it work this way (especially the `def only' part) ?

···

On 10/3/06, James Edward Gray II <james@grayproductions.net> wrote:

On Oct 3, 2006, at 11:38 AM, Tomasz Wegrzanowski wrote:
> On 10/3/06, James Edward Gray II <james@grayproductions.net>
> So why doesn't a.instance_eval { self.sortable } work ?
> I stand by my statement that pretty much nobody understands
> "protected".

Because that's a method call, not a function-style call.

--
Tomasz Wegrzanowski [ http://t-a-w.blogspot.com/ ]