作者
Ilya Klyuchnikov <ilya(点)klyuchnikov(在)gmail(点)com>
状态
草案
类型
标准跟踪
创建时间
2023-09-01

EEP 65: import_type 指令 #

摘要 #

本 EEP 提议一个新的 -import_type(Module, [Types]) 模块指令来导入远程类型。导入的类型可以像本地类型一样被引用,无需模块前缀。

理由和动机 #

该提案背后的理由与现有的 -import(Module, [Functions]) 指令一致。引入该指令的主要动机是:

  • 简化大量使用远程类型的模块。在模块频繁引用特定远程类型的情况下,使用其完全限定名称可能会变得冗长。例如,erl_lint 模块定义了一个 本地类型 anno() 来引用类型 erl_anno:anno()
  • 减少普遍类型中的冗余。在某些应用程序或项目中,某些类型可能非常常用,以至于使用其完整名称引用它们会变得过于冗长。一种常见的解决方法是将这些类型放置在头文件中,然后在应用程序的大部分模块中包含此头文件。一个例子:dialyzer.hrl 头文件中的常见类型。此方法的缺点是它会为工具(如 dialyzer 和类型检查器)创建额外的负载。每个模块最终都会有自己的本地类型副本,这可能会导致重复比较同一类型的不同版本,从而减慢分析速度。

它使得导出和导入类型与导出和导入函数对称。到目前为止,存在一种不对称性:类型可以导出但不能导入。

详细信息 #

引入一个新的模块指令

-import_type(Module, [T1/A1, ..., Tk/Ak]).

此处,Module 是一个原子(从中导入类型的模块的名称),Ti 是原子(类型的名称),Ai 是整数(元数)。导入的类型可以像本地类型一样被引用 - 无需任何模块前缀。

示例

-module(m1).
-import_type(common_types, [user/0, id/0]).

-spec get_user_id(user()) -> id().

对类型的引用应明确解析为本地定义的类型、预定义的内置类型或导入的类型。这施加了一些限制(类似于导入函数的限制)

  1. 导入指令不能覆盖内置类型。
  2. 类型 Ti/Ai 只能导入一次。
  3. 导入的类型不能在模块中本地重新定义。

这些限制由编译器验证(作为 erl_lint 阶段的一部分)。由工具来检查导入的远程类型是否存在并已导出,编译器不检查它。

限制示例

覆盖内置类型

-module(m2).
-import_type(m1, [binary/0]).

error: import directive overrides auto-imported builtin type binary/0

导入两次

-module(m).
-import_type(m1, [user/0]).
-import_type(m2, [user/0]).

error: type user/0 already imported from m1.

从同一模块导入两次

-module(m).
-import_type(m1, [a/0, b/0]).
-import_type(m1, [b/0, c/0]).

error: type b/0 already imported from m1

重新定义导入的类型

-module(m).
-import_type(m1, [user/0]).

-type user() :: {user, binary()}.

error: defining imported type user/0

参考实现 #

https://github.com/erlang/otp/pull/7618

该实现由三个逻辑部分组成:

  • 解析器(erl_parse)中的更改,以支持新指令。
  • erl_lint 中的更改,以强制执行限制(来自详细信息部分)
  • 导入类型的扩展由处理函数导入的同一编译器过程处理。因此,导入的函数和导入的类型以相同的方式处理。Dialyzer 已经获得了扩展的导入类型,所以它“开箱即用”。

向后兼容性 #

此更改向后兼容。现有代码不能具有建议的 import_type 属性,因为当前编译器无法解析它们。

版权 #

本文档置于公共领域或 CC0-1.0-Universal 许可之下,以较宽松者为准。