Why can't classes be used as modules?
Module
is the superclass of Class
:
Class.superclass
# => Module
In OOP, this implies that an instance of Class
can be used in every place where an instance of Module
can be used.
Surprisingly, this is not the case with Class
instances in Ruby:
class C end
c = C.new
module M end
# Let's do all the extend/include/prepend stuff with M!
c.extend M
C.include M
C.prepend M
# All worked fine until this line.
# Let's turn to classes now!
# First, get a class to work with.
class C_as_M end
C_as_M.class.superclass
# => Module # yes, C_as_M is an instance of a Module child
C_as_M.is_a? Module
# => true # yes, it is still a Module
# And now let's do the same extend/include/prepend stuff with C_as_M!
c.extend C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.include C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.prepend C_as_M
# => TypeError: wrong argument type Class (expected Module)
What is the reason for the violation of this OOP principle? Why can't classes be used as modules?
ruby class module superclass
add a comment |
Module
is the superclass of Class
:
Class.superclass
# => Module
In OOP, this implies that an instance of Class
can be used in every place where an instance of Module
can be used.
Surprisingly, this is not the case with Class
instances in Ruby:
class C end
c = C.new
module M end
# Let's do all the extend/include/prepend stuff with M!
c.extend M
C.include M
C.prepend M
# All worked fine until this line.
# Let's turn to classes now!
# First, get a class to work with.
class C_as_M end
C_as_M.class.superclass
# => Module # yes, C_as_M is an instance of a Module child
C_as_M.is_a? Module
# => true # yes, it is still a Module
# And now let's do the same extend/include/prepend stuff with C_as_M!
c.extend C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.include C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.prepend C_as_M
# => TypeError: wrong argument type Class (expected Module)
What is the reason for the violation of this OOP principle? Why can't classes be used as modules?
ruby class module superclass
Butdef MyClass end
,MyClass.superclass
returns#=> Object
.
– iGian
Nov 23 '18 at 16:27
@iGian It's not the same. You're asking the wrong object. You have to askMyClass.class.superclass
. Compare to:Integer.superclass # => Numeric
You're asking like5.superclass
– Min-Soo Pipefeet
Nov 23 '18 at 16:36
@iGian: Your code doesn't returnObject
, it raises aNoMethodError
exception sinceMyClass
evaluates tonil
andnil
doesn't have asuperclass
method.
– Jörg W Mittag
Nov 24 '18 at 9:06
@JörgWMittag, I actually made a typo, it isclass MyClass end; p MyClass.superclass #=> Object
, notdef
.
– iGian
Nov 24 '18 at 15:25
3
This is a great question!
– Cary Swoveland
Nov 24 '18 at 18:53
add a comment |
Module
is the superclass of Class
:
Class.superclass
# => Module
In OOP, this implies that an instance of Class
can be used in every place where an instance of Module
can be used.
Surprisingly, this is not the case with Class
instances in Ruby:
class C end
c = C.new
module M end
# Let's do all the extend/include/prepend stuff with M!
c.extend M
C.include M
C.prepend M
# All worked fine until this line.
# Let's turn to classes now!
# First, get a class to work with.
class C_as_M end
C_as_M.class.superclass
# => Module # yes, C_as_M is an instance of a Module child
C_as_M.is_a? Module
# => true # yes, it is still a Module
# And now let's do the same extend/include/prepend stuff with C_as_M!
c.extend C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.include C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.prepend C_as_M
# => TypeError: wrong argument type Class (expected Module)
What is the reason for the violation of this OOP principle? Why can't classes be used as modules?
ruby class module superclass
Module
is the superclass of Class
:
Class.superclass
# => Module
In OOP, this implies that an instance of Class
can be used in every place where an instance of Module
can be used.
Surprisingly, this is not the case with Class
instances in Ruby:
class C end
c = C.new
module M end
# Let's do all the extend/include/prepend stuff with M!
c.extend M
C.include M
C.prepend M
# All worked fine until this line.
# Let's turn to classes now!
# First, get a class to work with.
class C_as_M end
C_as_M.class.superclass
# => Module # yes, C_as_M is an instance of a Module child
C_as_M.is_a? Module
# => true # yes, it is still a Module
# And now let's do the same extend/include/prepend stuff with C_as_M!
c.extend C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.include C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.prepend C_as_M
# => TypeError: wrong argument type Class (expected Module)
What is the reason for the violation of this OOP principle? Why can't classes be used as modules?
ruby class module superclass
ruby class module superclass
edited Nov 26 '18 at 7:03
sawa
131k29205303
131k29205303
asked Nov 23 '18 at 15:57
Min-Soo PipefeetMin-Soo Pipefeet
19510
19510
Butdef MyClass end
,MyClass.superclass
returns#=> Object
.
– iGian
Nov 23 '18 at 16:27
@iGian It's not the same. You're asking the wrong object. You have to askMyClass.class.superclass
. Compare to:Integer.superclass # => Numeric
You're asking like5.superclass
– Min-Soo Pipefeet
Nov 23 '18 at 16:36
@iGian: Your code doesn't returnObject
, it raises aNoMethodError
exception sinceMyClass
evaluates tonil
andnil
doesn't have asuperclass
method.
– Jörg W Mittag
Nov 24 '18 at 9:06
@JörgWMittag, I actually made a typo, it isclass MyClass end; p MyClass.superclass #=> Object
, notdef
.
– iGian
Nov 24 '18 at 15:25
3
This is a great question!
– Cary Swoveland
Nov 24 '18 at 18:53
add a comment |
Butdef MyClass end
,MyClass.superclass
returns#=> Object
.
– iGian
Nov 23 '18 at 16:27
@iGian It's not the same. You're asking the wrong object. You have to askMyClass.class.superclass
. Compare to:Integer.superclass # => Numeric
You're asking like5.superclass
– Min-Soo Pipefeet
Nov 23 '18 at 16:36
@iGian: Your code doesn't returnObject
, it raises aNoMethodError
exception sinceMyClass
evaluates tonil
andnil
doesn't have asuperclass
method.
– Jörg W Mittag
Nov 24 '18 at 9:06
@JörgWMittag, I actually made a typo, it isclass MyClass end; p MyClass.superclass #=> Object
, notdef
.
– iGian
Nov 24 '18 at 15:25
3
This is a great question!
– Cary Swoveland
Nov 24 '18 at 18:53
But
def MyClass end
, MyClass.superclass
returns #=> Object
.– iGian
Nov 23 '18 at 16:27
But
def MyClass end
, MyClass.superclass
returns #=> Object
.– iGian
Nov 23 '18 at 16:27
@iGian It's not the same. You're asking the wrong object. You have to ask
MyClass.class.superclass
. Compare to: Integer.superclass # => Numeric
You're asking like 5.superclass
– Min-Soo Pipefeet
Nov 23 '18 at 16:36
@iGian It's not the same. You're asking the wrong object. You have to ask
MyClass.class.superclass
. Compare to: Integer.superclass # => Numeric
You're asking like 5.superclass
– Min-Soo Pipefeet
Nov 23 '18 at 16:36
@iGian: Your code doesn't return
Object
, it raises a NoMethodError
exception since MyClass
evaluates to nil
and nil
doesn't have a superclass
method.– Jörg W Mittag
Nov 24 '18 at 9:06
@iGian: Your code doesn't return
Object
, it raises a NoMethodError
exception since MyClass
evaluates to nil
and nil
doesn't have a superclass
method.– Jörg W Mittag
Nov 24 '18 at 9:06
@JörgWMittag, I actually made a typo, it is
class MyClass end; p MyClass.superclass #=> Object
, not def
.– iGian
Nov 24 '18 at 15:25
@JörgWMittag, I actually made a typo, it is
class MyClass end; p MyClass.superclass #=> Object
, not def
.– iGian
Nov 24 '18 at 15:25
3
3
This is a great question!
– Cary Swoveland
Nov 24 '18 at 18:53
This is a great question!
– Cary Swoveland
Nov 24 '18 at 18:53
add a comment |
2 Answers
2
active
oldest
votes
In OOP, this implies that an instance of Class can be used in every place where an instance of Module can be used.
You are confusing subtypes and subclasses, i.e. subtyping (which is about refinement of contracts) and inheritance (which is about differential code re-use).
In Ruby, inheritance creates a subclass, but not a subtype. (In fact, "type" is a concept that exists only in the programmer's head in Ruby.)
Class < Module
is one example of a subclass that is not a subtype, and StringIO
<: IO
is an example of a subtype that is not a subclass.
What is the reason for the violation of this OOP principle?
It's not an OO principle. Subtyping and OO are completely orthogonal. There are OO languages without subtyping as well as non-OO languages with subtyping.
Note: it would actually be easy to collapse modules and classes into one. They could inherit from each other both the same way as classes inherit from classes as well as the way classes inherit from modules.
2
And still the question: why classes can't be used as modules? I mean: what is the internal reason in Ruby not to let classes include/prepend other classes?
– Min-Soo Pipefeet
Nov 23 '18 at 19:26
Regarding the other topics: of course, one may argue that in Ruby classes weren't types and inheritance does not create subtypes, as by the authors of the book Programming Ruby 1.9 & 2.0. But the fact is (as pointed out by the same authors): "Inheritance allows you to create a class that is a refinement or specialization of another class. ... The basic mechanism of subclassing ... The child inherits all of the capabilities of its parent class - all the parent's instance methods are available in instances of the child." In other words: inheritance does create a subtype (OO or not).
– Min-Soo Pipefeet
Nov 24 '18 at 8:35
"And still the question: why classes can't be used as modules?" – That question is off-topic for Stack Overflow, since it can only be answered by Yukihiro Matsumoto himself; only he knows why he made that choice. If I had to make a guess, it would be too confusing to have two different kinds of inheritance involving the same language construct. The Self language has shown that you don't even need classes, you can collapse classes and objects into one construct (only objects), and Beta and gBeta have shown that you can unify objects, classes, and methods into one construct (patterns).
– Jörg W Mittag
Nov 24 '18 at 9:12
And yet, languages in which objects, classes, methods, and fields are four distinct things (with interfaces and mixins optionally added on top, plus another mechanism for large-scale structuring such as modules or packages) seem to dominate, because abstracting and unifying too much seems to confuse programmers. Even people who were involved with Beta admit that that it was hard to structure programs correctly when there was no distinction between methods and classes.
– Jörg W Mittag
Nov 24 '18 at 9:14
1
"..but you found out that that is not the case, that you can't use a class everywhere you can use a module" - Right, but that's not because of the inheritance. It's because of Ruby's deliberate distortion of the inheritance in this special case. Hence, the question for the rationale behind this distortion. (Btw: the question is absolutely in-topic, and Yukihiro Matsumoto is highly welcome to be a user of Stack Overflow, as well as other creators and maintainers of Ruby.)
– Min-Soo Pipefeet
Nov 24 '18 at 9:28
|
show 1 more comment
I guess, meanwhile I've figured out why classes can't be used as modules in Ruby.
Or to be more specific: why classes can't be included/prepended.
It's the same reason as why Ruby does not support multiple inheritance:
to avoid ambiguity/complexity in the ancestors hierarchy.
After a short explanation of how multiple inheritance affects the ancestors hierarchy, I will explain why including/prepending classes would introduce multiple inheritance or/and similar complexities through the back door.
With single inheritance, the ancestors hierarchy of a given class is simply a chain.
The chain may be long or short, but it's always just a linear chain of ancestor classes:
File.ancestors
=> [File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
Object.ancestors
=> [Object, Kernel, BasicObject]
BasicObject.ancestors
=> [BasicObject]
So looking up an instance variable or method is simple as that: look in the current class; if not found, go to the next ancestor and look there; if not found, go to the next ancestor and look there...
With multiple inheritance, the ancestors hierarchy can branch.
Hypothetically given
class A end
class B < A; end
class C < A; end
class D < B, C
end
produces the following ancestors graph of class D:
This increases complexity and ambiguity, it induces "the diamond problem":
- Do instances of D share instance variables in class A inherited via B and inherited via C?
- If they do not share them,
we need an extended syntax to designate
whether we want to access an instance variable in A via B or via C.
Ruby is designed to avoid this complexity. Hence, no multiple inheritance by design.
Including/prepending modules
is another mean of constructing/manipulating the ancestors hierarchy:
class MyBase < BasicObject; end
class C < MyBase; end
C.ancestors
=> [C, MyBase, BasicObject]
module IncludeMe end
C.include IncludeMe
C.ancestors
=> [C, IncludeMe, MyBase, BasicObject]
module PrependMe end
C.prepend PrependMe
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, BasicObject]
module Intermediate end
MyBase.include Intermediate
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, Intermediate, BasicObject]
Including/prepending modules only keeps the ancestors chain a simple chain.
No bad things happen.
Including/prepending classes
Now imagine IncludeMe
, PrependMe
, and Intermediate
were not modules but classes.
To keep it simple, I will stick to one class only:
class PrependMe
def to_s
"Hello from prepended #{super}!"
end
end
Keep in mind that PrependMe
inherits from Object
by default:
PrependMe.ancestors
# => [PrependMe, Object, Kernel, BasicObject]
Also, pay attention to that fact that in Ruby it's not possible to create baseless classes (BasicObject is the only baseless class):
class BaselessClass < nil # Try it!
end # You'll fail.
# => TypeError: superclass must be a Class (NilClass given)
So, each class X (except BasicObject) has an ancestors chain with at least two parts,
always ending with BasicObject:
X.ancestors
# => [X, ..., BasicObject]
# the shortest possible chain is [X, BasicObject]
# the usual chain is [X, ..., Object, Kernel, BasicObject]
So, what should the ancestors hierarchy of class C look like after C.prepend PrependMe
?
C.ancestors
=> [PrependMe, Object, Kernel, C, MyBase, BasicObject] ?
=> [PrependMe, C, Object, Kernel, MyBase, BasicObject] ?
=> [PrependMe, C, MyBase, Object, Kernel, BasicObject] ?
=> [PrependMe, C, MyBase, BasicObject] ? # Object and Kernel omitted on purpose
Or should the ancestors hierarchy even branch at PrependMe
into an own branch for Object
? With all the effects of the diamond problem.
One could reasonably argue for each of these options.
The answer depends on what you want to see as the result of (c = C.new).to_s
.
Obviously, including/prepending classes would introduce ambiguity and complexity similar to or even worse than these of multiple inheritance. And as in the case of multiple inheritance, it's a deliberate decision of Ruby to avoid this.
Apparently, this is the reason why including/appending classes is forbidden in Ruby.
You are assuming that including classes would work differently than including modules. If including classes worked the same way as including modules, the same linearization property would apply as for modules, and there would be no diamonds. Therefore, it would be perfectly possible to include classes the same way as modules. The problem with both this approach and yours, is that then the same language construct (classes) could be used for two different kinds of inheritance (classical class-based inheritance and linearized mixin inheritance) that work differently.
– Jörg W Mittag
Nov 24 '18 at 18:31
I guess (but I do not know) that this might be the reason to separate the two; to alleviate this confusion. (Although note that modules also serve a secondary purpose as namespaces, which interestingly is a trait that classes actually inherit from modules.) So, by my argument, there could even be three language constructs: namespaces, mixins, and classes.
– Jörg W Mittag
Nov 24 '18 at 18:33
Mixin inheritance is not multiple inheritance, it is, in fact, the exact dual: in multiple inheritance, a class appears once in the inheritance graph, and may have multiple superclasses, whereas in mixin inheritance, a class appears multiple times in the inheritance graph and each time has only one superclass.
– Jörg W Mittag
Nov 24 '18 at 18:35
The way mixins work in Ruby is as follows: Step 1: a new classM'
is created whose method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer point to the same place in memory as the method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer of the moduleM
. Step 2: the current superclass ofC
is made the superclass ofM'
. Step 3:M'
is made the superclass ofC
. You could perform exactly those same steps regardless of whetherM
is a module or class, with exactly the …
– Jörg W Mittag
Nov 24 '18 at 19:15
… same results. SinceM
is copied to a class anyway in Step 1, it really doesn't matter at all for the algorithm whetherM
is a class or module, it will work exactly the same. The only thing classes have that modules don't, is a superclass pointer, but that is not used forM
and overwritten forM'
, so it makes no difference.
– Jörg W Mittag
Nov 24 '18 at 19:16
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53449705%2fwhy-cant-classes-be-used-as-modules%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
In OOP, this implies that an instance of Class can be used in every place where an instance of Module can be used.
You are confusing subtypes and subclasses, i.e. subtyping (which is about refinement of contracts) and inheritance (which is about differential code re-use).
In Ruby, inheritance creates a subclass, but not a subtype. (In fact, "type" is a concept that exists only in the programmer's head in Ruby.)
Class < Module
is one example of a subclass that is not a subtype, and StringIO
<: IO
is an example of a subtype that is not a subclass.
What is the reason for the violation of this OOP principle?
It's not an OO principle. Subtyping and OO are completely orthogonal. There are OO languages without subtyping as well as non-OO languages with subtyping.
Note: it would actually be easy to collapse modules and classes into one. They could inherit from each other both the same way as classes inherit from classes as well as the way classes inherit from modules.
2
And still the question: why classes can't be used as modules? I mean: what is the internal reason in Ruby not to let classes include/prepend other classes?
– Min-Soo Pipefeet
Nov 23 '18 at 19:26
Regarding the other topics: of course, one may argue that in Ruby classes weren't types and inheritance does not create subtypes, as by the authors of the book Programming Ruby 1.9 & 2.0. But the fact is (as pointed out by the same authors): "Inheritance allows you to create a class that is a refinement or specialization of another class. ... The basic mechanism of subclassing ... The child inherits all of the capabilities of its parent class - all the parent's instance methods are available in instances of the child." In other words: inheritance does create a subtype (OO or not).
– Min-Soo Pipefeet
Nov 24 '18 at 8:35
"And still the question: why classes can't be used as modules?" – That question is off-topic for Stack Overflow, since it can only be answered by Yukihiro Matsumoto himself; only he knows why he made that choice. If I had to make a guess, it would be too confusing to have two different kinds of inheritance involving the same language construct. The Self language has shown that you don't even need classes, you can collapse classes and objects into one construct (only objects), and Beta and gBeta have shown that you can unify objects, classes, and methods into one construct (patterns).
– Jörg W Mittag
Nov 24 '18 at 9:12
And yet, languages in which objects, classes, methods, and fields are four distinct things (with interfaces and mixins optionally added on top, plus another mechanism for large-scale structuring such as modules or packages) seem to dominate, because abstracting and unifying too much seems to confuse programmers. Even people who were involved with Beta admit that that it was hard to structure programs correctly when there was no distinction between methods and classes.
– Jörg W Mittag
Nov 24 '18 at 9:14
1
"..but you found out that that is not the case, that you can't use a class everywhere you can use a module" - Right, but that's not because of the inheritance. It's because of Ruby's deliberate distortion of the inheritance in this special case. Hence, the question for the rationale behind this distortion. (Btw: the question is absolutely in-topic, and Yukihiro Matsumoto is highly welcome to be a user of Stack Overflow, as well as other creators and maintainers of Ruby.)
– Min-Soo Pipefeet
Nov 24 '18 at 9:28
|
show 1 more comment
In OOP, this implies that an instance of Class can be used in every place where an instance of Module can be used.
You are confusing subtypes and subclasses, i.e. subtyping (which is about refinement of contracts) and inheritance (which is about differential code re-use).
In Ruby, inheritance creates a subclass, but not a subtype. (In fact, "type" is a concept that exists only in the programmer's head in Ruby.)
Class < Module
is one example of a subclass that is not a subtype, and StringIO
<: IO
is an example of a subtype that is not a subclass.
What is the reason for the violation of this OOP principle?
It's not an OO principle. Subtyping and OO are completely orthogonal. There are OO languages without subtyping as well as non-OO languages with subtyping.
Note: it would actually be easy to collapse modules and classes into one. They could inherit from each other both the same way as classes inherit from classes as well as the way classes inherit from modules.
2
And still the question: why classes can't be used as modules? I mean: what is the internal reason in Ruby not to let classes include/prepend other classes?
– Min-Soo Pipefeet
Nov 23 '18 at 19:26
Regarding the other topics: of course, one may argue that in Ruby classes weren't types and inheritance does not create subtypes, as by the authors of the book Programming Ruby 1.9 & 2.0. But the fact is (as pointed out by the same authors): "Inheritance allows you to create a class that is a refinement or specialization of another class. ... The basic mechanism of subclassing ... The child inherits all of the capabilities of its parent class - all the parent's instance methods are available in instances of the child." In other words: inheritance does create a subtype (OO or not).
– Min-Soo Pipefeet
Nov 24 '18 at 8:35
"And still the question: why classes can't be used as modules?" – That question is off-topic for Stack Overflow, since it can only be answered by Yukihiro Matsumoto himself; only he knows why he made that choice. If I had to make a guess, it would be too confusing to have two different kinds of inheritance involving the same language construct. The Self language has shown that you don't even need classes, you can collapse classes and objects into one construct (only objects), and Beta and gBeta have shown that you can unify objects, classes, and methods into one construct (patterns).
– Jörg W Mittag
Nov 24 '18 at 9:12
And yet, languages in which objects, classes, methods, and fields are four distinct things (with interfaces and mixins optionally added on top, plus another mechanism for large-scale structuring such as modules or packages) seem to dominate, because abstracting and unifying too much seems to confuse programmers. Even people who were involved with Beta admit that that it was hard to structure programs correctly when there was no distinction between methods and classes.
– Jörg W Mittag
Nov 24 '18 at 9:14
1
"..but you found out that that is not the case, that you can't use a class everywhere you can use a module" - Right, but that's not because of the inheritance. It's because of Ruby's deliberate distortion of the inheritance in this special case. Hence, the question for the rationale behind this distortion. (Btw: the question is absolutely in-topic, and Yukihiro Matsumoto is highly welcome to be a user of Stack Overflow, as well as other creators and maintainers of Ruby.)
– Min-Soo Pipefeet
Nov 24 '18 at 9:28
|
show 1 more comment
In OOP, this implies that an instance of Class can be used in every place where an instance of Module can be used.
You are confusing subtypes and subclasses, i.e. subtyping (which is about refinement of contracts) and inheritance (which is about differential code re-use).
In Ruby, inheritance creates a subclass, but not a subtype. (In fact, "type" is a concept that exists only in the programmer's head in Ruby.)
Class < Module
is one example of a subclass that is not a subtype, and StringIO
<: IO
is an example of a subtype that is not a subclass.
What is the reason for the violation of this OOP principle?
It's not an OO principle. Subtyping and OO are completely orthogonal. There are OO languages without subtyping as well as non-OO languages with subtyping.
Note: it would actually be easy to collapse modules and classes into one. They could inherit from each other both the same way as classes inherit from classes as well as the way classes inherit from modules.
In OOP, this implies that an instance of Class can be used in every place where an instance of Module can be used.
You are confusing subtypes and subclasses, i.e. subtyping (which is about refinement of contracts) and inheritance (which is about differential code re-use).
In Ruby, inheritance creates a subclass, but not a subtype. (In fact, "type" is a concept that exists only in the programmer's head in Ruby.)
Class < Module
is one example of a subclass that is not a subtype, and StringIO
<: IO
is an example of a subtype that is not a subclass.
What is the reason for the violation of this OOP principle?
It's not an OO principle. Subtyping and OO are completely orthogonal. There are OO languages without subtyping as well as non-OO languages with subtyping.
Note: it would actually be easy to collapse modules and classes into one. They could inherit from each other both the same way as classes inherit from classes as well as the way classes inherit from modules.
answered Nov 23 '18 at 19:11
Jörg W MittagJörg W Mittag
291k63356550
291k63356550
2
And still the question: why classes can't be used as modules? I mean: what is the internal reason in Ruby not to let classes include/prepend other classes?
– Min-Soo Pipefeet
Nov 23 '18 at 19:26
Regarding the other topics: of course, one may argue that in Ruby classes weren't types and inheritance does not create subtypes, as by the authors of the book Programming Ruby 1.9 & 2.0. But the fact is (as pointed out by the same authors): "Inheritance allows you to create a class that is a refinement or specialization of another class. ... The basic mechanism of subclassing ... The child inherits all of the capabilities of its parent class - all the parent's instance methods are available in instances of the child." In other words: inheritance does create a subtype (OO or not).
– Min-Soo Pipefeet
Nov 24 '18 at 8:35
"And still the question: why classes can't be used as modules?" – That question is off-topic for Stack Overflow, since it can only be answered by Yukihiro Matsumoto himself; only he knows why he made that choice. If I had to make a guess, it would be too confusing to have two different kinds of inheritance involving the same language construct. The Self language has shown that you don't even need classes, you can collapse classes and objects into one construct (only objects), and Beta and gBeta have shown that you can unify objects, classes, and methods into one construct (patterns).
– Jörg W Mittag
Nov 24 '18 at 9:12
And yet, languages in which objects, classes, methods, and fields are four distinct things (with interfaces and mixins optionally added on top, plus another mechanism for large-scale structuring such as modules or packages) seem to dominate, because abstracting and unifying too much seems to confuse programmers. Even people who were involved with Beta admit that that it was hard to structure programs correctly when there was no distinction between methods and classes.
– Jörg W Mittag
Nov 24 '18 at 9:14
1
"..but you found out that that is not the case, that you can't use a class everywhere you can use a module" - Right, but that's not because of the inheritance. It's because of Ruby's deliberate distortion of the inheritance in this special case. Hence, the question for the rationale behind this distortion. (Btw: the question is absolutely in-topic, and Yukihiro Matsumoto is highly welcome to be a user of Stack Overflow, as well as other creators and maintainers of Ruby.)
– Min-Soo Pipefeet
Nov 24 '18 at 9:28
|
show 1 more comment
2
And still the question: why classes can't be used as modules? I mean: what is the internal reason in Ruby not to let classes include/prepend other classes?
– Min-Soo Pipefeet
Nov 23 '18 at 19:26
Regarding the other topics: of course, one may argue that in Ruby classes weren't types and inheritance does not create subtypes, as by the authors of the book Programming Ruby 1.9 & 2.0. But the fact is (as pointed out by the same authors): "Inheritance allows you to create a class that is a refinement or specialization of another class. ... The basic mechanism of subclassing ... The child inherits all of the capabilities of its parent class - all the parent's instance methods are available in instances of the child." In other words: inheritance does create a subtype (OO or not).
– Min-Soo Pipefeet
Nov 24 '18 at 8:35
"And still the question: why classes can't be used as modules?" – That question is off-topic for Stack Overflow, since it can only be answered by Yukihiro Matsumoto himself; only he knows why he made that choice. If I had to make a guess, it would be too confusing to have two different kinds of inheritance involving the same language construct. The Self language has shown that you don't even need classes, you can collapse classes and objects into one construct (only objects), and Beta and gBeta have shown that you can unify objects, classes, and methods into one construct (patterns).
– Jörg W Mittag
Nov 24 '18 at 9:12
And yet, languages in which objects, classes, methods, and fields are four distinct things (with interfaces and mixins optionally added on top, plus another mechanism for large-scale structuring such as modules or packages) seem to dominate, because abstracting and unifying too much seems to confuse programmers. Even people who were involved with Beta admit that that it was hard to structure programs correctly when there was no distinction between methods and classes.
– Jörg W Mittag
Nov 24 '18 at 9:14
1
"..but you found out that that is not the case, that you can't use a class everywhere you can use a module" - Right, but that's not because of the inheritance. It's because of Ruby's deliberate distortion of the inheritance in this special case. Hence, the question for the rationale behind this distortion. (Btw: the question is absolutely in-topic, and Yukihiro Matsumoto is highly welcome to be a user of Stack Overflow, as well as other creators and maintainers of Ruby.)
– Min-Soo Pipefeet
Nov 24 '18 at 9:28
2
2
And still the question: why classes can't be used as modules? I mean: what is the internal reason in Ruby not to let classes include/prepend other classes?
– Min-Soo Pipefeet
Nov 23 '18 at 19:26
And still the question: why classes can't be used as modules? I mean: what is the internal reason in Ruby not to let classes include/prepend other classes?
– Min-Soo Pipefeet
Nov 23 '18 at 19:26
Regarding the other topics: of course, one may argue that in Ruby classes weren't types and inheritance does not create subtypes, as by the authors of the book Programming Ruby 1.9 & 2.0. But the fact is (as pointed out by the same authors): "Inheritance allows you to create a class that is a refinement or specialization of another class. ... The basic mechanism of subclassing ... The child inherits all of the capabilities of its parent class - all the parent's instance methods are available in instances of the child." In other words: inheritance does create a subtype (OO or not).
– Min-Soo Pipefeet
Nov 24 '18 at 8:35
Regarding the other topics: of course, one may argue that in Ruby classes weren't types and inheritance does not create subtypes, as by the authors of the book Programming Ruby 1.9 & 2.0. But the fact is (as pointed out by the same authors): "Inheritance allows you to create a class that is a refinement or specialization of another class. ... The basic mechanism of subclassing ... The child inherits all of the capabilities of its parent class - all the parent's instance methods are available in instances of the child." In other words: inheritance does create a subtype (OO or not).
– Min-Soo Pipefeet
Nov 24 '18 at 8:35
"And still the question: why classes can't be used as modules?" – That question is off-topic for Stack Overflow, since it can only be answered by Yukihiro Matsumoto himself; only he knows why he made that choice. If I had to make a guess, it would be too confusing to have two different kinds of inheritance involving the same language construct. The Self language has shown that you don't even need classes, you can collapse classes and objects into one construct (only objects), and Beta and gBeta have shown that you can unify objects, classes, and methods into one construct (patterns).
– Jörg W Mittag
Nov 24 '18 at 9:12
"And still the question: why classes can't be used as modules?" – That question is off-topic for Stack Overflow, since it can only be answered by Yukihiro Matsumoto himself; only he knows why he made that choice. If I had to make a guess, it would be too confusing to have two different kinds of inheritance involving the same language construct. The Self language has shown that you don't even need classes, you can collapse classes and objects into one construct (only objects), and Beta and gBeta have shown that you can unify objects, classes, and methods into one construct (patterns).
– Jörg W Mittag
Nov 24 '18 at 9:12
And yet, languages in which objects, classes, methods, and fields are four distinct things (with interfaces and mixins optionally added on top, plus another mechanism for large-scale structuring such as modules or packages) seem to dominate, because abstracting and unifying too much seems to confuse programmers. Even people who were involved with Beta admit that that it was hard to structure programs correctly when there was no distinction between methods and classes.
– Jörg W Mittag
Nov 24 '18 at 9:14
And yet, languages in which objects, classes, methods, and fields are four distinct things (with interfaces and mixins optionally added on top, plus another mechanism for large-scale structuring such as modules or packages) seem to dominate, because abstracting and unifying too much seems to confuse programmers. Even people who were involved with Beta admit that that it was hard to structure programs correctly when there was no distinction between methods and classes.
– Jörg W Mittag
Nov 24 '18 at 9:14
1
1
"..but you found out that that is not the case, that you can't use a class everywhere you can use a module" - Right, but that's not because of the inheritance. It's because of Ruby's deliberate distortion of the inheritance in this special case. Hence, the question for the rationale behind this distortion. (Btw: the question is absolutely in-topic, and Yukihiro Matsumoto is highly welcome to be a user of Stack Overflow, as well as other creators and maintainers of Ruby.)
– Min-Soo Pipefeet
Nov 24 '18 at 9:28
"..but you found out that that is not the case, that you can't use a class everywhere you can use a module" - Right, but that's not because of the inheritance. It's because of Ruby's deliberate distortion of the inheritance in this special case. Hence, the question for the rationale behind this distortion. (Btw: the question is absolutely in-topic, and Yukihiro Matsumoto is highly welcome to be a user of Stack Overflow, as well as other creators and maintainers of Ruby.)
– Min-Soo Pipefeet
Nov 24 '18 at 9:28
|
show 1 more comment
I guess, meanwhile I've figured out why classes can't be used as modules in Ruby.
Or to be more specific: why classes can't be included/prepended.
It's the same reason as why Ruby does not support multiple inheritance:
to avoid ambiguity/complexity in the ancestors hierarchy.
After a short explanation of how multiple inheritance affects the ancestors hierarchy, I will explain why including/prepending classes would introduce multiple inheritance or/and similar complexities through the back door.
With single inheritance, the ancestors hierarchy of a given class is simply a chain.
The chain may be long or short, but it's always just a linear chain of ancestor classes:
File.ancestors
=> [File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
Object.ancestors
=> [Object, Kernel, BasicObject]
BasicObject.ancestors
=> [BasicObject]
So looking up an instance variable or method is simple as that: look in the current class; if not found, go to the next ancestor and look there; if not found, go to the next ancestor and look there...
With multiple inheritance, the ancestors hierarchy can branch.
Hypothetically given
class A end
class B < A; end
class C < A; end
class D < B, C
end
produces the following ancestors graph of class D:
This increases complexity and ambiguity, it induces "the diamond problem":
- Do instances of D share instance variables in class A inherited via B and inherited via C?
- If they do not share them,
we need an extended syntax to designate
whether we want to access an instance variable in A via B or via C.
Ruby is designed to avoid this complexity. Hence, no multiple inheritance by design.
Including/prepending modules
is another mean of constructing/manipulating the ancestors hierarchy:
class MyBase < BasicObject; end
class C < MyBase; end
C.ancestors
=> [C, MyBase, BasicObject]
module IncludeMe end
C.include IncludeMe
C.ancestors
=> [C, IncludeMe, MyBase, BasicObject]
module PrependMe end
C.prepend PrependMe
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, BasicObject]
module Intermediate end
MyBase.include Intermediate
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, Intermediate, BasicObject]
Including/prepending modules only keeps the ancestors chain a simple chain.
No bad things happen.
Including/prepending classes
Now imagine IncludeMe
, PrependMe
, and Intermediate
were not modules but classes.
To keep it simple, I will stick to one class only:
class PrependMe
def to_s
"Hello from prepended #{super}!"
end
end
Keep in mind that PrependMe
inherits from Object
by default:
PrependMe.ancestors
# => [PrependMe, Object, Kernel, BasicObject]
Also, pay attention to that fact that in Ruby it's not possible to create baseless classes (BasicObject is the only baseless class):
class BaselessClass < nil # Try it!
end # You'll fail.
# => TypeError: superclass must be a Class (NilClass given)
So, each class X (except BasicObject) has an ancestors chain with at least two parts,
always ending with BasicObject:
X.ancestors
# => [X, ..., BasicObject]
# the shortest possible chain is [X, BasicObject]
# the usual chain is [X, ..., Object, Kernel, BasicObject]
So, what should the ancestors hierarchy of class C look like after C.prepend PrependMe
?
C.ancestors
=> [PrependMe, Object, Kernel, C, MyBase, BasicObject] ?
=> [PrependMe, C, Object, Kernel, MyBase, BasicObject] ?
=> [PrependMe, C, MyBase, Object, Kernel, BasicObject] ?
=> [PrependMe, C, MyBase, BasicObject] ? # Object and Kernel omitted on purpose
Or should the ancestors hierarchy even branch at PrependMe
into an own branch for Object
? With all the effects of the diamond problem.
One could reasonably argue for each of these options.
The answer depends on what you want to see as the result of (c = C.new).to_s
.
Obviously, including/prepending classes would introduce ambiguity and complexity similar to or even worse than these of multiple inheritance. And as in the case of multiple inheritance, it's a deliberate decision of Ruby to avoid this.
Apparently, this is the reason why including/appending classes is forbidden in Ruby.
You are assuming that including classes would work differently than including modules. If including classes worked the same way as including modules, the same linearization property would apply as for modules, and there would be no diamonds. Therefore, it would be perfectly possible to include classes the same way as modules. The problem with both this approach and yours, is that then the same language construct (classes) could be used for two different kinds of inheritance (classical class-based inheritance and linearized mixin inheritance) that work differently.
– Jörg W Mittag
Nov 24 '18 at 18:31
I guess (but I do not know) that this might be the reason to separate the two; to alleviate this confusion. (Although note that modules also serve a secondary purpose as namespaces, which interestingly is a trait that classes actually inherit from modules.) So, by my argument, there could even be three language constructs: namespaces, mixins, and classes.
– Jörg W Mittag
Nov 24 '18 at 18:33
Mixin inheritance is not multiple inheritance, it is, in fact, the exact dual: in multiple inheritance, a class appears once in the inheritance graph, and may have multiple superclasses, whereas in mixin inheritance, a class appears multiple times in the inheritance graph and each time has only one superclass.
– Jörg W Mittag
Nov 24 '18 at 18:35
The way mixins work in Ruby is as follows: Step 1: a new classM'
is created whose method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer point to the same place in memory as the method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer of the moduleM
. Step 2: the current superclass ofC
is made the superclass ofM'
. Step 3:M'
is made the superclass ofC
. You could perform exactly those same steps regardless of whetherM
is a module or class, with exactly the …
– Jörg W Mittag
Nov 24 '18 at 19:15
… same results. SinceM
is copied to a class anyway in Step 1, it really doesn't matter at all for the algorithm whetherM
is a class or module, it will work exactly the same. The only thing classes have that modules don't, is a superclass pointer, but that is not used forM
and overwritten forM'
, so it makes no difference.
– Jörg W Mittag
Nov 24 '18 at 19:16
add a comment |
I guess, meanwhile I've figured out why classes can't be used as modules in Ruby.
Or to be more specific: why classes can't be included/prepended.
It's the same reason as why Ruby does not support multiple inheritance:
to avoid ambiguity/complexity in the ancestors hierarchy.
After a short explanation of how multiple inheritance affects the ancestors hierarchy, I will explain why including/prepending classes would introduce multiple inheritance or/and similar complexities through the back door.
With single inheritance, the ancestors hierarchy of a given class is simply a chain.
The chain may be long or short, but it's always just a linear chain of ancestor classes:
File.ancestors
=> [File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
Object.ancestors
=> [Object, Kernel, BasicObject]
BasicObject.ancestors
=> [BasicObject]
So looking up an instance variable or method is simple as that: look in the current class; if not found, go to the next ancestor and look there; if not found, go to the next ancestor and look there...
With multiple inheritance, the ancestors hierarchy can branch.
Hypothetically given
class A end
class B < A; end
class C < A; end
class D < B, C
end
produces the following ancestors graph of class D:
This increases complexity and ambiguity, it induces "the diamond problem":
- Do instances of D share instance variables in class A inherited via B and inherited via C?
- If they do not share them,
we need an extended syntax to designate
whether we want to access an instance variable in A via B or via C.
Ruby is designed to avoid this complexity. Hence, no multiple inheritance by design.
Including/prepending modules
is another mean of constructing/manipulating the ancestors hierarchy:
class MyBase < BasicObject; end
class C < MyBase; end
C.ancestors
=> [C, MyBase, BasicObject]
module IncludeMe end
C.include IncludeMe
C.ancestors
=> [C, IncludeMe, MyBase, BasicObject]
module PrependMe end
C.prepend PrependMe
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, BasicObject]
module Intermediate end
MyBase.include Intermediate
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, Intermediate, BasicObject]
Including/prepending modules only keeps the ancestors chain a simple chain.
No bad things happen.
Including/prepending classes
Now imagine IncludeMe
, PrependMe
, and Intermediate
were not modules but classes.
To keep it simple, I will stick to one class only:
class PrependMe
def to_s
"Hello from prepended #{super}!"
end
end
Keep in mind that PrependMe
inherits from Object
by default:
PrependMe.ancestors
# => [PrependMe, Object, Kernel, BasicObject]
Also, pay attention to that fact that in Ruby it's not possible to create baseless classes (BasicObject is the only baseless class):
class BaselessClass < nil # Try it!
end # You'll fail.
# => TypeError: superclass must be a Class (NilClass given)
So, each class X (except BasicObject) has an ancestors chain with at least two parts,
always ending with BasicObject:
X.ancestors
# => [X, ..., BasicObject]
# the shortest possible chain is [X, BasicObject]
# the usual chain is [X, ..., Object, Kernel, BasicObject]
So, what should the ancestors hierarchy of class C look like after C.prepend PrependMe
?
C.ancestors
=> [PrependMe, Object, Kernel, C, MyBase, BasicObject] ?
=> [PrependMe, C, Object, Kernel, MyBase, BasicObject] ?
=> [PrependMe, C, MyBase, Object, Kernel, BasicObject] ?
=> [PrependMe, C, MyBase, BasicObject] ? # Object and Kernel omitted on purpose
Or should the ancestors hierarchy even branch at PrependMe
into an own branch for Object
? With all the effects of the diamond problem.
One could reasonably argue for each of these options.
The answer depends on what you want to see as the result of (c = C.new).to_s
.
Obviously, including/prepending classes would introduce ambiguity and complexity similar to or even worse than these of multiple inheritance. And as in the case of multiple inheritance, it's a deliberate decision of Ruby to avoid this.
Apparently, this is the reason why including/appending classes is forbidden in Ruby.
You are assuming that including classes would work differently than including modules. If including classes worked the same way as including modules, the same linearization property would apply as for modules, and there would be no diamonds. Therefore, it would be perfectly possible to include classes the same way as modules. The problem with both this approach and yours, is that then the same language construct (classes) could be used for two different kinds of inheritance (classical class-based inheritance and linearized mixin inheritance) that work differently.
– Jörg W Mittag
Nov 24 '18 at 18:31
I guess (but I do not know) that this might be the reason to separate the two; to alleviate this confusion. (Although note that modules also serve a secondary purpose as namespaces, which interestingly is a trait that classes actually inherit from modules.) So, by my argument, there could even be three language constructs: namespaces, mixins, and classes.
– Jörg W Mittag
Nov 24 '18 at 18:33
Mixin inheritance is not multiple inheritance, it is, in fact, the exact dual: in multiple inheritance, a class appears once in the inheritance graph, and may have multiple superclasses, whereas in mixin inheritance, a class appears multiple times in the inheritance graph and each time has only one superclass.
– Jörg W Mittag
Nov 24 '18 at 18:35
The way mixins work in Ruby is as follows: Step 1: a new classM'
is created whose method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer point to the same place in memory as the method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer of the moduleM
. Step 2: the current superclass ofC
is made the superclass ofM'
. Step 3:M'
is made the superclass ofC
. You could perform exactly those same steps regardless of whetherM
is a module or class, with exactly the …
– Jörg W Mittag
Nov 24 '18 at 19:15
… same results. SinceM
is copied to a class anyway in Step 1, it really doesn't matter at all for the algorithm whetherM
is a class or module, it will work exactly the same. The only thing classes have that modules don't, is a superclass pointer, but that is not used forM
and overwritten forM'
, so it makes no difference.
– Jörg W Mittag
Nov 24 '18 at 19:16
add a comment |
I guess, meanwhile I've figured out why classes can't be used as modules in Ruby.
Or to be more specific: why classes can't be included/prepended.
It's the same reason as why Ruby does not support multiple inheritance:
to avoid ambiguity/complexity in the ancestors hierarchy.
After a short explanation of how multiple inheritance affects the ancestors hierarchy, I will explain why including/prepending classes would introduce multiple inheritance or/and similar complexities through the back door.
With single inheritance, the ancestors hierarchy of a given class is simply a chain.
The chain may be long or short, but it's always just a linear chain of ancestor classes:
File.ancestors
=> [File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
Object.ancestors
=> [Object, Kernel, BasicObject]
BasicObject.ancestors
=> [BasicObject]
So looking up an instance variable or method is simple as that: look in the current class; if not found, go to the next ancestor and look there; if not found, go to the next ancestor and look there...
With multiple inheritance, the ancestors hierarchy can branch.
Hypothetically given
class A end
class B < A; end
class C < A; end
class D < B, C
end
produces the following ancestors graph of class D:
This increases complexity and ambiguity, it induces "the diamond problem":
- Do instances of D share instance variables in class A inherited via B and inherited via C?
- If they do not share them,
we need an extended syntax to designate
whether we want to access an instance variable in A via B or via C.
Ruby is designed to avoid this complexity. Hence, no multiple inheritance by design.
Including/prepending modules
is another mean of constructing/manipulating the ancestors hierarchy:
class MyBase < BasicObject; end
class C < MyBase; end
C.ancestors
=> [C, MyBase, BasicObject]
module IncludeMe end
C.include IncludeMe
C.ancestors
=> [C, IncludeMe, MyBase, BasicObject]
module PrependMe end
C.prepend PrependMe
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, BasicObject]
module Intermediate end
MyBase.include Intermediate
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, Intermediate, BasicObject]
Including/prepending modules only keeps the ancestors chain a simple chain.
No bad things happen.
Including/prepending classes
Now imagine IncludeMe
, PrependMe
, and Intermediate
were not modules but classes.
To keep it simple, I will stick to one class only:
class PrependMe
def to_s
"Hello from prepended #{super}!"
end
end
Keep in mind that PrependMe
inherits from Object
by default:
PrependMe.ancestors
# => [PrependMe, Object, Kernel, BasicObject]
Also, pay attention to that fact that in Ruby it's not possible to create baseless classes (BasicObject is the only baseless class):
class BaselessClass < nil # Try it!
end # You'll fail.
# => TypeError: superclass must be a Class (NilClass given)
So, each class X (except BasicObject) has an ancestors chain with at least two parts,
always ending with BasicObject:
X.ancestors
# => [X, ..., BasicObject]
# the shortest possible chain is [X, BasicObject]
# the usual chain is [X, ..., Object, Kernel, BasicObject]
So, what should the ancestors hierarchy of class C look like after C.prepend PrependMe
?
C.ancestors
=> [PrependMe, Object, Kernel, C, MyBase, BasicObject] ?
=> [PrependMe, C, Object, Kernel, MyBase, BasicObject] ?
=> [PrependMe, C, MyBase, Object, Kernel, BasicObject] ?
=> [PrependMe, C, MyBase, BasicObject] ? # Object and Kernel omitted on purpose
Or should the ancestors hierarchy even branch at PrependMe
into an own branch for Object
? With all the effects of the diamond problem.
One could reasonably argue for each of these options.
The answer depends on what you want to see as the result of (c = C.new).to_s
.
Obviously, including/prepending classes would introduce ambiguity and complexity similar to or even worse than these of multiple inheritance. And as in the case of multiple inheritance, it's a deliberate decision of Ruby to avoid this.
Apparently, this is the reason why including/appending classes is forbidden in Ruby.
I guess, meanwhile I've figured out why classes can't be used as modules in Ruby.
Or to be more specific: why classes can't be included/prepended.
It's the same reason as why Ruby does not support multiple inheritance:
to avoid ambiguity/complexity in the ancestors hierarchy.
After a short explanation of how multiple inheritance affects the ancestors hierarchy, I will explain why including/prepending classes would introduce multiple inheritance or/and similar complexities through the back door.
With single inheritance, the ancestors hierarchy of a given class is simply a chain.
The chain may be long or short, but it's always just a linear chain of ancestor classes:
File.ancestors
=> [File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
Object.ancestors
=> [Object, Kernel, BasicObject]
BasicObject.ancestors
=> [BasicObject]
So looking up an instance variable or method is simple as that: look in the current class; if not found, go to the next ancestor and look there; if not found, go to the next ancestor and look there...
With multiple inheritance, the ancestors hierarchy can branch.
Hypothetically given
class A end
class B < A; end
class C < A; end
class D < B, C
end
produces the following ancestors graph of class D:
This increases complexity and ambiguity, it induces "the diamond problem":
- Do instances of D share instance variables in class A inherited via B and inherited via C?
- If they do not share them,
we need an extended syntax to designate
whether we want to access an instance variable in A via B or via C.
Ruby is designed to avoid this complexity. Hence, no multiple inheritance by design.
Including/prepending modules
is another mean of constructing/manipulating the ancestors hierarchy:
class MyBase < BasicObject; end
class C < MyBase; end
C.ancestors
=> [C, MyBase, BasicObject]
module IncludeMe end
C.include IncludeMe
C.ancestors
=> [C, IncludeMe, MyBase, BasicObject]
module PrependMe end
C.prepend PrependMe
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, BasicObject]
module Intermediate end
MyBase.include Intermediate
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, Intermediate, BasicObject]
Including/prepending modules only keeps the ancestors chain a simple chain.
No bad things happen.
Including/prepending classes
Now imagine IncludeMe
, PrependMe
, and Intermediate
were not modules but classes.
To keep it simple, I will stick to one class only:
class PrependMe
def to_s
"Hello from prepended #{super}!"
end
end
Keep in mind that PrependMe
inherits from Object
by default:
PrependMe.ancestors
# => [PrependMe, Object, Kernel, BasicObject]
Also, pay attention to that fact that in Ruby it's not possible to create baseless classes (BasicObject is the only baseless class):
class BaselessClass < nil # Try it!
end # You'll fail.
# => TypeError: superclass must be a Class (NilClass given)
So, each class X (except BasicObject) has an ancestors chain with at least two parts,
always ending with BasicObject:
X.ancestors
# => [X, ..., BasicObject]
# the shortest possible chain is [X, BasicObject]
# the usual chain is [X, ..., Object, Kernel, BasicObject]
So, what should the ancestors hierarchy of class C look like after C.prepend PrependMe
?
C.ancestors
=> [PrependMe, Object, Kernel, C, MyBase, BasicObject] ?
=> [PrependMe, C, Object, Kernel, MyBase, BasicObject] ?
=> [PrependMe, C, MyBase, Object, Kernel, BasicObject] ?
=> [PrependMe, C, MyBase, BasicObject] ? # Object and Kernel omitted on purpose
Or should the ancestors hierarchy even branch at PrependMe
into an own branch for Object
? With all the effects of the diamond problem.
One could reasonably argue for each of these options.
The answer depends on what you want to see as the result of (c = C.new).to_s
.
Obviously, including/prepending classes would introduce ambiguity and complexity similar to or even worse than these of multiple inheritance. And as in the case of multiple inheritance, it's a deliberate decision of Ruby to avoid this.
Apparently, this is the reason why including/appending classes is forbidden in Ruby.
edited Nov 25 '18 at 4:55
answered Nov 24 '18 at 11:21
Min-Soo PipefeetMin-Soo Pipefeet
19510
19510
You are assuming that including classes would work differently than including modules. If including classes worked the same way as including modules, the same linearization property would apply as for modules, and there would be no diamonds. Therefore, it would be perfectly possible to include classes the same way as modules. The problem with both this approach and yours, is that then the same language construct (classes) could be used for two different kinds of inheritance (classical class-based inheritance and linearized mixin inheritance) that work differently.
– Jörg W Mittag
Nov 24 '18 at 18:31
I guess (but I do not know) that this might be the reason to separate the two; to alleviate this confusion. (Although note that modules also serve a secondary purpose as namespaces, which interestingly is a trait that classes actually inherit from modules.) So, by my argument, there could even be three language constructs: namespaces, mixins, and classes.
– Jörg W Mittag
Nov 24 '18 at 18:33
Mixin inheritance is not multiple inheritance, it is, in fact, the exact dual: in multiple inheritance, a class appears once in the inheritance graph, and may have multiple superclasses, whereas in mixin inheritance, a class appears multiple times in the inheritance graph and each time has only one superclass.
– Jörg W Mittag
Nov 24 '18 at 18:35
The way mixins work in Ruby is as follows: Step 1: a new classM'
is created whose method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer point to the same place in memory as the method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer of the moduleM
. Step 2: the current superclass ofC
is made the superclass ofM'
. Step 3:M'
is made the superclass ofC
. You could perform exactly those same steps regardless of whetherM
is a module or class, with exactly the …
– Jörg W Mittag
Nov 24 '18 at 19:15
… same results. SinceM
is copied to a class anyway in Step 1, it really doesn't matter at all for the algorithm whetherM
is a class or module, it will work exactly the same. The only thing classes have that modules don't, is a superclass pointer, but that is not used forM
and overwritten forM'
, so it makes no difference.
– Jörg W Mittag
Nov 24 '18 at 19:16
add a comment |
You are assuming that including classes would work differently than including modules. If including classes worked the same way as including modules, the same linearization property would apply as for modules, and there would be no diamonds. Therefore, it would be perfectly possible to include classes the same way as modules. The problem with both this approach and yours, is that then the same language construct (classes) could be used for two different kinds of inheritance (classical class-based inheritance and linearized mixin inheritance) that work differently.
– Jörg W Mittag
Nov 24 '18 at 18:31
I guess (but I do not know) that this might be the reason to separate the two; to alleviate this confusion. (Although note that modules also serve a secondary purpose as namespaces, which interestingly is a trait that classes actually inherit from modules.) So, by my argument, there could even be three language constructs: namespaces, mixins, and classes.
– Jörg W Mittag
Nov 24 '18 at 18:33
Mixin inheritance is not multiple inheritance, it is, in fact, the exact dual: in multiple inheritance, a class appears once in the inheritance graph, and may have multiple superclasses, whereas in mixin inheritance, a class appears multiple times in the inheritance graph and each time has only one superclass.
– Jörg W Mittag
Nov 24 '18 at 18:35
The way mixins work in Ruby is as follows: Step 1: a new classM'
is created whose method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer point to the same place in memory as the method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer of the moduleM
. Step 2: the current superclass ofC
is made the superclass ofM'
. Step 3:M'
is made the superclass ofC
. You could perform exactly those same steps regardless of whetherM
is a module or class, with exactly the …
– Jörg W Mittag
Nov 24 '18 at 19:15
… same results. SinceM
is copied to a class anyway in Step 1, it really doesn't matter at all for the algorithm whetherM
is a class or module, it will work exactly the same. The only thing classes have that modules don't, is a superclass pointer, but that is not used forM
and overwritten forM'
, so it makes no difference.
– Jörg W Mittag
Nov 24 '18 at 19:16
You are assuming that including classes would work differently than including modules. If including classes worked the same way as including modules, the same linearization property would apply as for modules, and there would be no diamonds. Therefore, it would be perfectly possible to include classes the same way as modules. The problem with both this approach and yours, is that then the same language construct (classes) could be used for two different kinds of inheritance (classical class-based inheritance and linearized mixin inheritance) that work differently.
– Jörg W Mittag
Nov 24 '18 at 18:31
You are assuming that including classes would work differently than including modules. If including classes worked the same way as including modules, the same linearization property would apply as for modules, and there would be no diamonds. Therefore, it would be perfectly possible to include classes the same way as modules. The problem with both this approach and yours, is that then the same language construct (classes) could be used for two different kinds of inheritance (classical class-based inheritance and linearized mixin inheritance) that work differently.
– Jörg W Mittag
Nov 24 '18 at 18:31
I guess (but I do not know) that this might be the reason to separate the two; to alleviate this confusion. (Although note that modules also serve a secondary purpose as namespaces, which interestingly is a trait that classes actually inherit from modules.) So, by my argument, there could even be three language constructs: namespaces, mixins, and classes.
– Jörg W Mittag
Nov 24 '18 at 18:33
I guess (but I do not know) that this might be the reason to separate the two; to alleviate this confusion. (Although note that modules also serve a secondary purpose as namespaces, which interestingly is a trait that classes actually inherit from modules.) So, by my argument, there could even be three language constructs: namespaces, mixins, and classes.
– Jörg W Mittag
Nov 24 '18 at 18:33
Mixin inheritance is not multiple inheritance, it is, in fact, the exact dual: in multiple inheritance, a class appears once in the inheritance graph, and may have multiple superclasses, whereas in mixin inheritance, a class appears multiple times in the inheritance graph and each time has only one superclass.
– Jörg W Mittag
Nov 24 '18 at 18:35
Mixin inheritance is not multiple inheritance, it is, in fact, the exact dual: in multiple inheritance, a class appears once in the inheritance graph, and may have multiple superclasses, whereas in mixin inheritance, a class appears multiple times in the inheritance graph and each time has only one superclass.
– Jörg W Mittag
Nov 24 '18 at 18:35
The way mixins work in Ruby is as follows: Step 1: a new class
M'
is created whose method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer point to the same place in memory as the method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer of the module M
. Step 2: the current superclass of C
is made the superclass of M'
. Step 3: M'
is made the superclass of C
. You could perform exactly those same steps regardless of whether M
is a module or class, with exactly the …– Jörg W Mittag
Nov 24 '18 at 19:15
The way mixins work in Ruby is as follows: Step 1: a new class
M'
is created whose method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer point to the same place in memory as the method table pointer, class variable table pointer, constant table pointer, and instance variable table pointer of the module M
. Step 2: the current superclass of C
is made the superclass of M'
. Step 3: M'
is made the superclass of C
. You could perform exactly those same steps regardless of whether M
is a module or class, with exactly the …– Jörg W Mittag
Nov 24 '18 at 19:15
… same results. Since
M
is copied to a class anyway in Step 1, it really doesn't matter at all for the algorithm whether M
is a class or module, it will work exactly the same. The only thing classes have that modules don't, is a superclass pointer, but that is not used for M
and overwritten for M'
, so it makes no difference.– Jörg W Mittag
Nov 24 '18 at 19:16
… same results. Since
M
is copied to a class anyway in Step 1, it really doesn't matter at all for the algorithm whether M
is a class or module, it will work exactly the same. The only thing classes have that modules don't, is a superclass pointer, but that is not used for M
and overwritten for M'
, so it makes no difference.– Jörg W Mittag
Nov 24 '18 at 19:16
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53449705%2fwhy-cant-classes-be-used-as-modules%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
But
def MyClass end
,MyClass.superclass
returns#=> Object
.– iGian
Nov 23 '18 at 16:27
@iGian It's not the same. You're asking the wrong object. You have to ask
MyClass.class.superclass
. Compare to:Integer.superclass # => Numeric
You're asking like5.superclass
– Min-Soo Pipefeet
Nov 23 '18 at 16:36
@iGian: Your code doesn't return
Object
, it raises aNoMethodError
exception sinceMyClass
evaluates tonil
andnil
doesn't have asuperclass
method.– Jörg W Mittag
Nov 24 '18 at 9:06
@JörgWMittag, I actually made a typo, it is
class MyClass end; p MyClass.superclass #=> Object
, notdef
.– iGian
Nov 24 '18 at 15:25
3
This is a great question!
– Cary Swoveland
Nov 24 '18 at 18:53