I am having trouble with some fundamental concepts in Ruby, specifically the interchangeability of a subclass for the superclass.
According to the Ruby documentation on classes, "Class" inherits from "Module". https://ruby-doc.org/core-2.5.3/Class.html
class MyClassTest end
MyClassTest.is_a? Module # => true
However, when trying to use the module
keyword to reopen a class defined with the keyword class
, you get a TypeError that the class is not a module.
class MyClassTest end
module MyClassTest end # => TypeError: MyClassTest is not a module
This SO question has some excellent discussion surrounding subclasses vs subtypes, but I think it has lead me to more questions: Why can't classes be used as modules?
Generally, since Ruby is dynamically typed, I am confused by the existence of TypeErrors.
Specifically, in this case, I am extra confused as to how Ruby inheritance can result in a TypeError where the subclass cannot be substituted for the superclass. In my mind, subclassing is equivalent to subtyping in Ruby since the subclass will inherit the interface (methods and public attributes) of the superclass.
My current guess is that TypeError's are raised by the core Ruby library when certain assertions fail, and these TypeErrors don't necessarily have anything to do with Ruby's dynamic typing system, which is to say that typing is not a first-class concept in Ruby. The linked SO question raises excellent points about the diamond problem with multiple class inheritance, so it makes sense that Ruby would prevent the interchangeable usage of modules and classes when using the module
or class
keyword. Still, it feels like there are inconsistencies in my understanding of Ruby.
How can a "Class" input result in a TypeError when a "Module" object is expected?
Basic assertions are
Class
is a Class
(Class.is_a?(Class) #=> true
)Class
is a Module
(Class.is_a?(Module) #=> true
)Class
is a Class
(Class.new.is_a?(Class) #=> true
)Class
is a Module
(Class.new.is_a?(Module) #=> true
)Module
is a Class
(Module.is_a?(Class) #=> true
)Module
is a Module
(Module.is_a?(Module) #=> true
)Module
is a Module
(Module.new.is_a?(Module) #=> true
)Module
is not a Class
(Module.new.is_a?(Class) #=> false
)Class
is an instance of Class
but not and instance of the class Module
(Class.new.instance_of?(Module) #=> false
)module
is a declaration for an instance of the class Module
just as class
is a declaration for an instance of the class Class
.
If this were a method it might look like
def module(name,&block)
raise TypeError if defined?(name) && !const_get(name).instance_of?(Module)
obj = defined?(name) ? const_get(name) : const_set(name, Module.new)
obj.instance_eval(&block)
end
TypeError
exists to prevent ambiguity.
As in your case by using class MyClassTest
you have created an instance of the class Class
and that instance is called MyTestClass
.
If you were also allowed to use module MyTestClass
, in the same global context, then during usage I would be unaware if when calling MyClassTest
I would be calling the Class
or the Module
.
The basic (very basic) difference is a Class
can be instantiated (have instances) however a Module
cannot.
For instance
Class.new.new #creates an instance of the anonymous class created by Class.new
Module.new.new # results in NoMethodError