Ruby 继承和类型

Posted

技术标签:

【中文标题】Ruby 继承和类型【英文标题】:Ruby inheritance and typing 【发布时间】:2020-08-31 11:37:35 【问题描述】:

我在 Ruby 中的一些基本概念上遇到了麻烦,特别是子类与超类的可互换性。

根据关于类的 Ruby 文档,“类”继承自“模块”。 https://ruby-doc.org/core-2.5.3/Class.html

class MyClassTest end
MyClassTest.is_a? Module # => true

但是,当尝试使用 module 关键字重新打开使用关键字 class 定义的类时,您会收到一个类型错误,表明该类不是模块。

class MyClassTest end
module MyClassTest end # => TypeError: MyClassTest is not a module

这个 SO question 围绕子类与子类型进行了一些精彩的讨论,但我认为它让我提出了更多问题: Why can't classes be used as modules?

一般来说,由于 Ruby 是动态类型的,我对 TypeErrors 的存在感到困惑。

具体来说,在这种情况下,我对 Ruby 继承如何导致子类无法替换超类的 TypeError 感到更加困惑。在我看来,子类化相当于 Ruby 中的子类型化,因为子类将继承超类的接口(方法和公共属性)。

我目前的猜测是,当某些断言失败时,核心 Ruby 库会引发 TypeError,而这些 TypeError 不一定与 Ruby 的动态类型系统有关,也就是说类型不是一流的Ruby 中的概念。链接的 SO 问题对多类继承的菱形问题提出了很好的观点,因此在使用 moduleclass 关键字时,Ruby 会阻止模块和类的可互换使用是有道理的。不过,感觉我对 Ruby 的理解还是有不一致的地方。

当需要“模块”对象时,“类”输入如何导致 TypeError?

【问题讨论】:

【参考方案1】:

基本断言是

ClassClass (Class.is_a?(Class) #=> true) ClassModule (Class.is_a?(Module) #=> true) Class 类的实例是Class (Class.new.is_a?(Class) #=> true) Class 类的实例是Module (Class.new.is_a?(Module) #=> true) ModuleClass (Module.is_a?(Class) #=> true) 由于ModuleModule (Module.is_a?(Module) #=> true) Module 类的实例是Module (Module.new.is_a?(Module) #=> true) 但是Module 类的实例不是Class (Module.new.is_a?(Class) #=> false) Class 类的实例是 Class 的实例,但不是 Module 类的实例 (Class.new.instance_of?(Module) #=> false)

moduleModule 类实例的声明,正如classClass 类实例的声明一样。

如果这是一个方法,它可能看起来像

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 的存在是为了防止歧义。

在您的情况下,通过使用class MyClassTest,您创建了类Class 的实例,该实例称为MyTestClass

如果您也被允许在相同的全局上下文中使用module MyTestClass,那么在使用过程中我将不知道在调用MyClassTest 时我会调用Class 还是Module

基本(非常基本)的区别是Class 可以实例化(有实例),但Module 不能。

例如

Class.new.new #creates an instance of the anonymous class created by Class.new
Module.new.new # results in NoMethodError

【讨论】:

我明白了,这提供了一些很好的上下文。但是,基于这些断言:“类是模块(Class.is_a?(Module)#=> true)”,“类的实例是模块(Class.new.is_a?(Module)#=> true)" 我仍然对 " class MyClassTest end module MyClassTest end " 结果如何 => TypeError: MyClassTest is not a module 对我来说,模块和类定义必须是唯一的以避免歧义是有道理的,但是因为我们能够“重新打开”类定义,但对于为什么我们不能将类定义作为模块重新打开,我仍然有些困惑。 @BrandonChow 已更新,更加清晰。虽然 Class 的实例是 Module 它不是 Module 的实例【参考方案2】:

我认为混淆的第一点是用法定义之间的区别。

以下代码定义一个类:

class C; end

如果我看到上面的代码,我希望以后能够实例化C:

C.new

但是,假设 C 已经被定义为一个模块:

# In one file:
module C; end

# Later in a different file:
class C; end    # C is actually a module

C.new           # fails

Ruby 在 C 被重新定义为一个类的地方出现问题(C 的定义冲突),而不是让程序继续到使用 C 的地方。

越早发现问题的好处通常是越早发现错误,就越容易找到并修复其根本原因(在这个例子中,也许 C 毕竟应该是一个类,因此真正的问题是module C 定义)。

您的第二个问题是,我认为为什么一个类不能始终用作模块,例如为什么禁止以下内容:

class C; end
class A
  include C
end

我认为答案是编程语言从概念开始,然后使用各种结构实现。我们可以这样描述类和模块:

类表示具有数据和行为的对象(经典 OOP 定义)。 模块是行为的集合。

include 关键字使用模块的行为扩展了一个类。原则上,可以只采用一个类的方法并将它们添加到另一个类。但是这个操作没有意义,因为一个类是一个对象,它的行为是一起的。只取行为违背了类的概念。

还有其他编程语言在这个问题上采取不同的立场。例如,在 javascript 中,任何函数都可以从任何类中取出并与任何其他类的实例一起调用。这在某些情况下很方便,但在其他情况下很难调试。

【讨论】:

在 JavaScript 中,类并不是真正的东西。 JavaScript 中的类是可能定义也可能不定义其他函数的第一类函数的语法糖。

以上是关于Ruby 继承和类型的主要内容,如果未能解决你的问题,请参考以下文章

Ruby类实例变量和继承

JDBC连接字符串中类和方法的Ruby继承

Ruby类和实例变量跨继承生命周期演示

ruby Ruby继承

继承在 Ruby 中是如何工作的?

从 Ruby 中的模块/mixins 继承类方法