此 EEP 提供关于如何允许支持启用和禁用语言特性的建议。例如,这将允许用户在新的或提议的语言特性最终确定之前,试用、评论并提出更改建议。当引入新的向后不兼容特性时,它还可以避免使用新语言特性,从而使其可以在更慢的过渡中进行,例如,以逐个文件为基础。目前,这主要集中在语言本身的更改上,但它也可以用于运行时中的更改。
其他语言支持尝试不同语言特性的可能性。请参阅 Kjell Winblad 的Erlang 实验性语言特性调查报告,了解一些示例。此报告作为附录逐字包含。
我们希望通过添加新结构并删除或更改现有结构的语义来发展 Erlang/OTP。在将新特性最终确定为语言的一部分之前,允许用户在更大范围内尝试该特性,而无需运行 Erlang/OTP 的单独分支,这很方便。这将更容易测试新特性并促进反馈。在新特性成为 Erlang/OTP 的永久部分之前,应该可以选择使用它。
同样,应该可以不使用新特性,尤其是更改语义或删除现有结构的特性。这样做的好处是,升级 OTP 时不会强制用户进行代码库的转换,而是可以以更及时的方式完成。
这也将导致一种统一的方式来记录和引入各个级别的实验性特性,即语言、运行时、应用程序和 API。
我们希望能够在编译期间和运行时控制启用或禁用的特性。可以通过编译器/运行时的选项以及正在编译的模块中的指令来实现此控制。当所需的特性不存在时,可以发出良好的错误消息。
可以在下图看到特性的生命周期。
+--------------+ +----------+
| Experimental ------> | Approved |
+---.-------.--+ +-----.----+
| | |
| | |
| | |
| | |
+-----v----+ | +-----v-----+
| Rejected | +--------> | Permanent |
+----------+ +-----------+
除了启用和禁用特性的可能性之外,默认情况下可以启用特性。还将有多种方式来获取有关特性的信息(详情如下)。其中一些总结在下表中。
状态 | 默认 | 可控 | 可用 |
---|---|---|---|
实验性 | 禁用 | 是 | 是 |
已批准 | 启用 | 是 | 是 |
永久 | 启用 | 否 | 是 |
已拒绝 | 禁用 | 否 | 否 |
注释
FEATURE_AVAILABLE
和 erl_features
模块中的函数来查看可用状态。需要将用于启用和禁用特性或获取有关特性的信息的修改添加到至少以下模块或通用区域
erlc
- 选项处理erl_scan
- 关键字处理epp
- 指令处理和关键字集的更改erl_parse
(可能)- 特定特性的新语法规则erl_lint
- 特定特性的更改erl_expand_records
- 特定特性的更改beam_asm
compile
模块中用于处理选项的函数-feature(..).
指令erl_eval
- 特定特性的更改将添加一个新模块 erl_features
,以提供有关特性的详细信息以及对特性处理的支持函数。
在下面,我们在示例中使用了一些不存在的特性。在这一点上,这些特性在没有进一步解释的情况下,是 maybe_expr
(请参阅 EEP49)、module_alias
和 ieee754float
。
erlc
的选项 #编译器包装器 erlc
应扩展四个新选项,两个用于启用和禁用特性,两个用于获取有关特性的信息。前两个都以特性名称(原子)作为参数。允许多个实例。
-enable-feature <特性名称>
打开选定的特性。-disable-feature <特性名称>
关闭选定的特性。-list-features
显示当前特性的列表和简短描述。describe-feature <特性名称>
显示特性的更长描述。编译器将理解其他 +
样式选项。
+'{enable_feature,<特性名称>}'
– 请参阅上文。+'{disable_feature,<特性名称>}'
– 请参阅上文。+warn_keywords
– 为在某些现有特性中用作关键字的原子生成警告。+nowarn_keywords
– 阻止如上所述的警告。all
来启用或禁用所有可用特性。注意:前两个 +
选项的替代方法是具有三个元素的元组,即 {feature, enable | disable, <特性名称>}
。这具有与 -feature
指令的格式更加一致的优点(请参阅下文)。
erlc -enable-feature module_alias
表示在编译文件时使用特性 module_alias
。erlc -enable-feature ieee754float -disable-feature module_alias
表示在编译文件时使用特性 ieee754float
,但不使用特性 module_alias
。实际上,使用 module_alias
的实例应该因此生成错误。添加预处理器宏以启用检查特定特性是否可用或已启用。我们添加两个预定义宏
FEATURE_AVAILABLE(F)
– 当特性 F
在当前版本中可用时为 true
。对于未知特性,这将为 false
。FEATURE_ENABLED(F)
– 当特性 F
在代码中的当前位置启用时为 true
。对于未知特性,这将为 false
。同时具有这两个宏的用例是,可以使用 FEATURE_AVAILABLE
来确定特性是否可用,如果可用,则启用它。这将更容易编写在较长时间内适用于多个 OTP 版本的代码。然后,可以将宏 FEATURE_ENABLED
用于具有替代实现的代码段。
-if(?FEATURE_AVAILABLE(maybe_expr)).
%% Use the feature when available
-feature(enable, maybe_expr).
-endif.
-if(?FEATURE_ENABLED(maybe_expr)).
%% code that use the feature
-else.
%% alternative code not using the feature
-endif.
%% ..the above also allows simple negative tests
-if(not ?FEATURE_ENABLED(ieee754float)).
..
-endif.
compile
中函数的选项 #采用 options 参数的 compile
中的函数(即 file/2
、forms/2
、noenv_file/2
和 noenv_forms/2
)应进行扩展,以便 {enable_feature, atom()}
和 {disable_feature, atom()}
选项也被识别。
-feature(enable|disable, <特性>)
指令 #添加一个带有两个参数的新 -feature(..)
指令。仅允许在 -module(..)
声明之后和文件中到任何使用语法的指令的前缀中,例如,记录定义、-export(..).
或函数定义。允许预处理器指令、宏定义和包含,但是如果其中任何一个包含/导致上述任何一个,则前缀将结束。前缀概念扩展到涵盖包含的文件,这意味着前缀可以在包含的文件中处于活动状态和结束状态。
如果第一个参数是 enable
(disable
),则为正在编译的模块启用(禁用)第二个参数给定的特性。
允许多个 -feature
指令的实例。模块中的 -feature
指令的实例将优先于给编译器的选项。实际上,启用和禁用特性将具有后写获胜语义。
编译模块时,当特性已启用时,即使模块中没有实际使用该特性,该特性也将被视为已使用。
与 erlc
中用于编译模块的选项类似,在启动运行时时(例如,使用 erl
),我们应该能够指定允许的特性。这意味着,当加载模块时,可能会由于使用(即,使用某个特性编译)我们不允许的特性而被拒绝。这样做的原因是,人们可能希望在测试和开发期间允许使用特性,但在生产中允许使用特性时要更加小心。
这些选项的名称应与 erlc
的选项相同,即 -enable-feature
和 -disable-feature
。
启动后无法更改已启用特性的集合。
名为 erl_features
的模块用于获取有关已知特性的状态的信息。
用于获取有关特性的信息的新函数
features() -> [atom()]
feature_info(atom()) -> FeatureInfoMap
when
Description :: string(),
Type :: extension | backwards_incompatible_change,
FeatureInfoMap ::
#{description := Description,
short := Description,
type := Type,
keywords := [atom()],
experimental => Release,
approved => Release,
permanent => Release,
rejected => Release,
status := experimental
| approved
| permanent
| rejected
}
Release :: non_neg_integer()
%% As above, but give the feature info for a given release
feature_info(atom(), Release) -> Result
键的描述
description
- 特性的详细描述。short
- 简短的单行简介,描述特性。type
- 特性的性质,即保守扩展或向后不兼容的更改。keywords
- 特性引入的新关键字。status
- 特性的当前状态,每个状态都有一个对应的键,说明特性何时进入该状态。
请注意,所有键 experimental
、approved
、permanent
和 rejected
都不会出现,而只会显示上述生命周期图中当前状态之前的键。
这对内部和外部工具很有用,可以用于
-feature(..)
指令的实例erl_features
模块还将包含用于实际处理特性的支持函数,例如,动态关键字处理,但这些是与实现相关的,供内部使用,因此此处不再详细记录。
Meta
的新块中记录已使用的特性来实现。在运行时尝试加载时,将对照运行时中启用的特性检查已使用的特性。如果要加载的模块使用了未启用的特性,则将不允许加载。对新的 maybe .. else .. end
表达式(如 EEP49 中所述)的支持将使用特性机制来实现。这将使用特性名称 maybe_expr
完成,并且最初将具有 experimental
状态。这将为社区提供一个很好的机会来试用并提供反馈,然后再将其永久纳入该语言。
当编译使用 maybe
的模块时,需要启用特性 maybe_expr
。这可以通过几种方式完成
erlc
的选项,即 erlc -enable-feature maybe_expr
+
选项的可能性,即 erlc +'{enable_feature,maybe_expr}'
-feature(enable, maybe_expr).
为了简化代码库的转换或允许在(早期)版本中使用该特性(该特性在这些版本中不可用),可以使用引入的宏。可以使用上述编译器选项启用特性。或者,使用以下代码,如果定义了 use_maybe
,则可以启用该特性。
-ifdef(use_maybe).
-feature(enabled, maybe_expr).
-endif.
-if(?FEATURE_ENABLED(maybe_expr)).
%% Code using the maybe expression
foo(..) ->
maybe
X ?= ..
end.
-else.
%% Alternative (old?) implementation not using maybe
foo(..) ->
..
-endif.
如果模块在启用 maybe_expr
的情况下编译,这将记录在 beam 文件中(在新的 Meta
块中)。要允许在运行时加载模块,必须使用 enable-feature
选项启用特性 maybe_expr
。
不同 OTP 版本和具有特性的模块方面的一些可能场景
Meta
块中记录的特性编译的模块可以加载到(可能是较旧的)不知道该块存在的 OTP 版本中。可能还有其他因素阻止加载,例如,使用新的 BEAM 指令。-feature
指令的模块无法由(可能是较旧的)不知道特性的 OTP 版本编译。由于格式(带有两个参数)与属性(一个参数)的格式不同,因此将生成错误。实现目前正在进行中。目前支持以下内容
erlc
的长选项-enable-feature ..
-disable-feature ..
erlc
的 +
选项,例如,+'{enable_feature, maybe_expr}'
+warn_keywords
和 +nowarn_keywords
到 erlc
。从 erl_lint
生成的警告。-feature(enable, ..).
-feature(disable, ..).
这些仅允许在文件的已定义前缀中使用。compile:file/2
中的特性选项FEATURE_AVAILABLE
和 FEATURE_ENABLED
,两者都是 1 元。当看到 -feature
指令的实例时,当启用特性的集合发生变化时,宏 FEATURE_ENABLED
会动态更改。erl_features
模块的大部分,包括本文档中描述的函数和支持函数,用于处理关键字(保留字)集中的更改。Meta
)添加到 beam 文件中。因此,该块包含有关编译模块时使用的特性(如选项集所见,而不是特性是否实际存在)的信息。将来可以扩展该块以包含其他元信息。erl
提供选项来启用和禁用特性。使用未启用特性的模块将不会加载到系统中。+warn_keywords
到 erlc
)。gcc
的 -std=..
选项,用于指定要编译的语言标准,可以向 erlc
等添加类似的选项。简而言之,这将是一种命名特定版本中默认启用的所有语言特性的集合的方式。使用 -lang=otp24
命名选项 -lang
意味着我们希望使用 OTP24 中默认启用的所有特性(并且仅使用这些特性)编译输入文件,即使 erlc
来自 OTP25 版本也是如此。因此,将不允许在 OTP25 中添加的任何特性。但是,如果添加 enable-feature
选项,例如,erlc -lang=otp24 -enable-feature module_alias
,则允许。这是 Kjell Winblad 的原始报告 报告。其中一些内容应复制到文档的其他位置,因为它提供了有关其他语言的良好背景并奠定了基础。
Erlang 目前不支持选择性地使用并非 Erlang 语言正式组成部分的实验性语言特性。支持这样做可以帮助用户试用和试验对该语言的潜在扩展,而无需将此扩展添加到主语言中。本报告研究了在其他语言中选择性地包含实验性语言特性的支持情况,以及在 Erlang 中这种支持可能是什么样子。
Pyhton 支持使语言扩展或更改在成为强制性之前是可选的。Python 模块 __future__
定义了几个特性名称,如下所示
FeatureName = _Feature(OptionalRelease, MandatoryRelease,
CompilerFlag)
Python 有一个 特殊语句,需要将其放置在 python 模块的顶部附近,以在特定模块中启用语言特性。启用已成为强制性的特性的语句不起作用。
在 Python 中,特性名称永远不会从 __future__
模块中删除,这意味着 __future__
模块包含语言更改的历史记录。
Python 的未来导入语句和 __future__
模块的一些优点是
__future__
模块访问可通过编程访问的语言更改历史记录。工具可以使用此历史记录,例如,删除不再必要的 from __future__ import x
语句。Ruby 没有对实验性特性提供特殊支持(实验性特性只是在文档中记录为实验性的)。有关更多信息,请参阅 此问题,该问题建议使用命令行标志来启用实验性特性。
Rust 有一个 特殊语法,用于激活实验性语言特性。这是一个示例
#![feature(box_syntax)]
fn main() {
let five = box 5;
}
此类特性在 Rust 的术语中称为不稳定的。它们可能随时更改或消失。
为当前编译单元(crate)激活该特性。
Haskell 可以使用文件头中的编译指示来激活某些语言特性
{-# LANGUAGE TemplateHaskell #-}
Java 允许用户测试计划用于更高版本的特性。这需要通过在编译 Java 文件时将编译标志传递给编译器来启用
javac --enable-preview --release 12 # 其他标志
上面的行可以在发布 Java 版本 12 之前的早期 Java 版本中启用计划用于 Java 版本 12 的语言特性。为了限制预览特性的使用,在使用 -enable-preview
标志编译 Java 程序时,还必须传递 -enable-preview
。使用预览特性时,始终会打印警告消息。
当在 java 中为特定版本启用预览特性时,会获得该版本的所有预览特性。无法选择单个特性。
功能在被认为足够好、无需修改即可包含之前,不会作为预览功能发布。因此,对预览功能的更改相对较少,但可能会发生。
详情请参阅此处。
以下方法可用于激活实验性功能
-compile().
指令,compile
模块中编译函数的选项之一,或者erlc
的编译标志。用于启用实验性功能的选项/标志可以包含前缀和实验性功能的名称
示例
-compile([{enable_experimental, pinning_operator}]).
compile:file(File, [{enable_experimental,pinning_operator}])
erlc -enable_experimental_pinning_operator
在上述示例中,enable_experimental
是前缀,而 pinning_operator
是实验性功能的名称。
所有当前存在和过去存在的实验性功能都可以在一个特殊的模块中“记录”(类似于 Python)。假设这个模块名为 experimental_features
。该模块可以是公开的,允许外部工具使用该模块,也可以是内部的,如果我们想能够更改其 API。
experimental_features
模块具有可用于获取实验性功能信息的函数
list_experimental_features() -> [atom()].
此函数返回所有当前存在和曾经存在的实验性功能的列表。
get_experimental_feature_info(FeatureName) -> Result when
FeatureName :: atom(),
description :: string(),
Type :: extension | backwords_incompatible_change,
Result :: missing | FeatureInfoMap,
FeatureInfoMap :: #{optional_release := ReleaseNr,
status := experimental | %% May be removed
%% or changed
{remove_planned, ReleaseNr,
AdditionalInfo :: string()} |
{inclusion_planned, ReleaseNr} |
{removed, ReleaseNr,
AdditionalInfo :: string()},
{included, ReleaseNr}
%% list of compiler options that needs
%% to be given to activate this feature
%% (this can be useful, for example, when
%% one experimental feature depend on another)
compiler_options := list()
},
ReleaseNr : {Major :: integer(),
Minor :: integer(),
Patch :: integer(),
Label :: string()}.
此函数返回与给定功能名称相关的信息。
外部和内部工具可以使用从 experimental_features
模块获取的信息来
-compile()
指令不再需要时,自动从中删除 {enable_experimental,x} 元组。类似于 Java,我们可以发出有关已编译模块中使用实验性功能的信息。当运行包含实验性功能的模块时,VM 可以使用此信息打印警告消息。我们还可以在运行使用实验性功能编译的代码时强制使用特殊标志。
本文档放置在公共领域或 CC0-1.0-Universal 许可之下,以较宽松者为准。