Issue #5632 has been updated by boris_stitnicky (Boris Stitnicky).


Back to the original issue, having
module A; class X; def hello; puts 'hello' end end end
module B; include A end

then using "class X" statement inside B module does not behave as you say,
"X = defined?(X) ? X : Class.new", but as
"X = self.constants(false).include?(:X) ? X : Class.new",
which is undocumented and yet has to be memorized - a surefire recipe for
unwanted language exploration session in irb.

Let me say in detail what problem I was solving back then. It was a Petri
net with place and transition classes in its namespace:

module Petri
  class Place # define Petri net place (marking property etc.)
  end
  class Transition # defines Petri net transition (firing, enabled-disabled etc.)
  end
  class Net # a collection of connected Places and Transitions
  end
end

Having defined this, I said to myself: Now I'll make a special kind of a Petri net,
that can do some addtional tricks:

module ChemicalPetri
  include Petri
  NA = 6.022e23 # teach it Avogadro's number
  class Transition
    # teach transitions Arrhenius equation
  end
end

In fact, I expected to work on a deep subclass of 'Petri' module. But "class Transition"
gave me a brand new class silently, and there I had to forget about chemical equations
and hit irb. After 1 day, I figured out I have to write:
  class Transition < Transition
    # teach transitions Arrhenius equation
  end

Yet, "class Transition" was the first thing that jumped to my mind to get what I wanted.
From retrospective, there are 3 logically justified behaviors for "class Transition"
statement in this situation:

1. Opening the ancestor's class, explicitly:
class Petri::Transition; # do modifications
end
This is most "logical", because Transition constant is already there, but
opening ancestor's assets in offspring modules is hardly a good habit.
2. Creation of a brand new class, explicitly:
Transition = Class.new; class Transition; # do modifications
end
Less logical, but justifiable behavior, encouraging bad habits less.
3. Operation on a subclass, explicitly:
class Transition < Transition; # do modifications
end
Perhaps least logical, but I suspect that most frequently needed behavior.

I lean towards concluding, that in this situation "class X" statement is
always intuitively ambiguous and perhaps should always warn, explaining
what exactly is it doing, no matter which of the 3 behaviors is chosen
in Ruby implementation.

User should explicitly either ask for the parent's class (class A::X),
or explicitly create a new class (X = Class.new), or explicitly subclass
parent's X (class X < X). Since "class X < X" requires good understanding
of what's going on behind the scenes, perhaps there should be a new
statement(s) controlling this kind of subclassing behavior, something like
"subclass_also X", "deep_subclass_also X". (These are really not good
suggestions)

In sum, I'm trying to convey my feelings that once Ruby is used as a
math language to make slightly more complex object models, deep subclassing
might be an everyday need and should be provided for in the language
itself, rather than expecting users to write their own gems for this.
----------------------------------------
Feature #5632: Attempt to open included class shades it instead.
https://bugs.ruby-lang.org/issues/5632#change-26552

Author: boris_stitnicky (Boris Stitnicky)
Status: Assigned
Priority: Normal
Assignee: mame (Yusuke Endoh)
Category: 
Target version: 3.0


# Hello everyone. I'm not a very advanced ruby user, and I
# would like to provide and outsider report on certain ruby
# behavior that might surprise newbies.

module A
  class X
    def hello; puts 'hello' end
  end
end

module B
  include A
end

B::X.new.hello
=> hello
# As expected.

# But when I tried to add new functionality to X, ...
module B
  class X
    def goodbye; puts 'goodbye' end
  end
end

B::X.new.hello
=> NoMethodError

# I was surprised, that my .hello method disappeared,
# when all I was trying to do, was to improve X in B.
# I actually somehow expected to work on a subclass
# of X, like this:

module C
  include A
  class X < X
    def goodbye; puts 'goodbye' end
  end
end

# My suggestions are:
# 1. I consider 'class X < X' syntax a little bit
#    mysterious. How about making this a default
#    behavior for 'class X' statements?
# 2. If the above is not considered beneficial, I
#    would welcome if 'class X' statement warned
#    when shadowing an existing name. People might
#    often assume that they are opening an existing
#    class, rather than getting a brand new one
#    shadowing the previous one. If people really
#    want a brand new shadowing class without warning
#    they could use explicit 'X = Class.new'.


-- 
http://bugs.ruby-lang.org/