我是 Eiffel 的新手,我试图了解“原始”类型(例如 INTEGER_32、REAL_64 等)是如何实现的。 我对 EIFFEL 库类源文件中的循环依赖感到困惑。
基本类型被实现为非扩展父类的“扩展”版本。 例如,INTEGER_32 是 INTEGER_REF_32 的扩展子级。 (请注意,“扩展”并不意味着“的实例”。扩展类是一个类,而不是一个对象。)
INTEGER_REF_32 包含其中扩展的 INTEGER_32 实例。 INTEGER_REF_32 对象中的某处必须存在扩展整数,这是有道理的,但这意味着父对象包含其子对象的实例。
当你查看特征的定义时,会变得更加混乱:
在 INTEGER_32 中,名为“as_integer_64”的功能/方法被简单地重新定义为“Result := Precurser”,其中 Precurser 表示使用父级的功能。 在父级(即 INTEGER_REF_32)中,“as_integer_64”被定义为“Result := item.as_integer_64”,其中“item”是扩展的 INTEGER_32! 换句话说,子进程调用其父进程的特征,而父进程调用子进程的特征。 我也很困惑为什么扩展版本明确地重新定义了“as_integer_64”,而这似乎是一个等效的定义。
我希望看到原始类型的实现细节终止,并显示某些功能是内部定义的。相反,我发现了这些循环定义。显然,编译器对原始类型的了解比源文本文件中显示的更多。当编译器看到它所知道的原始类型时,它是否会忽略源文本,或者 Precurser 在这种情况下是否具有不同的含义? 重新定义是对编译器的某种提示,还是让解析器满意的东西?
对基本类型中的特征进行递归定义的原因之一是可重用性。详细讨论可以在这篇论文中找到(不确定网上有没有):
亚历山大·V·科格坚科夫。基本类型和可重用性的实现。 – WOON’98 国际会议论文集,圣彼得堡,1998 年。
简而言之,如果一个基本类被用作其他类的祖先,那么该类将继承所有功能,如 item、+ 等。功能声明也取自基本类型。因为它们是使用基本类型上的功能调用来定义的,所以编译器知道如何生成代码。
实际上,如果您查看 INTEGER_32 的源代码,您会发现所有例程都是“内置”实现的,这意味着 Eiffel 编译器正在为这些功能提供实现。
由于EiffelBase(现在称为FreeELKS)设计的历史原因,我们为每个基本类型有两个类:X和X_REF。如果我们必须重做核心库而不用担心向后兼容性,我们只会保留 X。不需要 X_REF 类。将 X_REF 类视为 CELL [X] 数据类型,即 X 的包装器。
现在这些内置函数的 Eiffel Software 实现非常具体。使用 Precursor 看起来很奇怪,但这样做是为了允许旧的代码模式工作,即使规范不再允许这些代码模式。同样,如果我们必须放弃向后兼容性,我们将保持真正内置的实现,而不显示编译器实际会做什么。
本质上,不存在真正的递归定义。在 INTEGER_32_REF 中,“item”被视为 INTEGER_32 类型的属性,而在后代中,它被视为返回自身的查询。这就是为什么人们应该忽略 INTEGER_32_REF 类的存在,它只是一个 CELL [INTGER_32_REF]。