作者
Richard A. O'Keefe <ok(at)cs(dot)otago(dot)ac(dot)nz> , Per Gustafsson <pergu(at)it(dot)uu(dot)se>
状态
草案
类型
标准跟踪
创建于
2007-08-10
Erlang 版本
OTP_R12B-0

EEP 5:使用 export_to 实现更通用的封装 #

摘要 #

此 EEP 描述了一个名为 export_to 的新指令,它允许模块精确指定哪些其他模块可以调用模块中定义的函数。这为封装提供了一个非常精细的原始操作,允许程序员更直接地控制代码的使用方式。

这是 Richard O’Keefe 最初提出的一个想法。

规范 #

这是 -export_to 指令的语法

-export_to([m,...], [f/a,...]).

其中 [m,...] 是模块名称列表,而 [f/a,…] 是函数名称/元数对的列表。

这意味着函数 f/a 可以从模块 m 调用。无论调用是静态调用、动态调用还是 apply 调用都应该无关紧要。

除了限制为指定的模块集之外,这些函数应该像使用 -export 导出到世界的函数一样运作,,对这些函数的调用应该始终调用该函数的最新版本。

当列表只有一个元素时,可以将其缩写为该元素,因此

-export_to(m, [f/a,...]).
-export_to(m, f/a).
-export_to([m,...], f/a).

是允许的缩写。

一个函数可以显式导出到任意数量的模块。Erlang 编译器应该允许一个函数既使用 -export_to 显式导出到一个或多个模块,又使用 -export 导出到世界,以方便过渡,但默认情况下应该对此发出警告。现有的 export_all 标志也应该与显式导出兼容。

动机 #

Erlang 中的模块有多种角色。模块是编译和代码重载的单元。它也是封装的单元,因为隐藏函数不被其他函数调用的唯一方法是使其成为模块中的局部函数。模块还为函数定义了单独的命名空间。模块的这些丰富的角色使得难以以一种既能很好地封装又能保持模块小而专注的方式来构建 Erlang 应用程序。

大多数用 Erlang 编写的应用程序都由几个较小的模块组成,尽管应用程序有一个公共 API,它是模块导出函数的子集,但究竟哪些函数是公共 API 的一部分只能在注释或文档中指定,因为函数要么被导出以允许任何人调用它们,要么不被导出,这意味着它们只能在模块内部调用,但有时我们希望有更多的控制权,例如,此函数应该只从本应用程序中的其他模块调用,或者此函数应该只从 shell 调用。

-export_to 指令允许程序员表达这些限制,运行时系统随后会强制执行这些限制,以便读者可以信任它们。 -export_to 指令不是要取代 -export 指令,而是当程序员知道所有预期的协作者时的替代方案。

此功能的最初灵感来自 Eiffel 编程语言控制类的特性对其他类的可见性的方式,因此 Bertrand Meyer 值得称赞。当然,这意味着这个想法是“经过验证的技术”。

基本原理 #

在设计 -export_to 语法时有一些选择,例如,是否允许 m 为模块列表,或者我们是否应该有一个 export_to 列表,其中每个条目都是一个模块、函数/元数对。使用建议的语法的一个原因是它很容易被读作

导出到模块 m 此函数列表 [f/a]

考虑这个问题的一种方法是存在一个“导出”矩阵,其中行由模块索引,列由局部函数索引。有时按行切片很方便(行为模块可以调用这些回调),有时按列切片很方便(实用函数可以被我的应用程序中的所有这些模块调用)。最初的设计重点是回调,以便可以将它们导出到行为,而无需将它们导出到世界。从那时起,很明显导出到应用程序也是一件方便的事情。因此,让我们允许程序员以最清楚地表达他们意图的任何方式编写矩阵。

另一个问题是,我们是否应该为指定常见的导出模式提供一些语法糖,例如将一组函数导出到应用程序中的所有模块,或者导出函数以使其能够应用该函数或使能够更新该函数的代码。

事实上,使用此提案导出到应用程序已经很容易了。当您开发应用程序 foo 时,创建一个文件 foo_modules.hrl,内容如下

-define(_FOO_`_MODULES`,
        [ mod1
        , mod2
        ...
        ]).

然后在文件中您可以编写

-include('_foo_`_modules.hrl').
-export_to(_FOO_`_MODULES`, [f/a,...]).

一旦我们有了使用这个基本构建块的一些经验,可能值得讨论对该提案的进一步补充。

也许值得评论的一个问题是,调用受限导出的函数就像任何其他远程调用一样。似乎没有理由让它不同。特别是,如果模块 A 最初将函数导出到世界,并且模块 B 调用它,然后模块 A 的作者决定将其限制为 B,则 B 不应更改。

向后兼容性 #

添加 -export_to 指令应该是完全向后兼容的,因为它不是合法的属性,目前会导致语法错误。

实现 #

此功能尚未实现,但以下是我们认为实现应该满足的一些目标

  • -export_to 函数的普通静态调用应该与对其他 -export_to 函数的调用成本相同。

  • 其他调用的性能不应受到 -export_to 调用引入的影响。

  • 每个 (m,f,a) 矩阵条目的空间成本应为 O(1)。

可以通过将处理此功能的大部分机制放在加载程序中,并且仅对动态调用使用动态检查来实现这些目标。