作者
Dániel Szoboszlay <[email protected]>
状态
最终版/28.0,已在 OTP 28 版本中实现
类型
标准跟踪
创建日期
2024-07-01
Erlang 版本
OTP-28.0
发布历史
https://erlangforums.com/t/eep-70-non-filtering-generators/3937 https://github.com/erlang/otp/pull/8625

EEP 70: 严格和宽松的生成器 #

摘要 #

本 EEP 提议为所有现有的生成器(列表、位串和映射)添加一个新的严格变体。当前存在的生成器是宽松的:它们会忽略右侧表达式中与左侧模式不匹配的项。另一方面,严格生成器将因异常 badmatch 而失败。

理由 #

严格生成器的动机是,宽松的生成器可能会隐藏推导式输入数据中意外元素的存在。例如,考虑以下代码片段:

[{User, Email} || #{user := User, email := Email} <- all_users()]

此列表推导式将过滤掉没有电子邮件地址的用户。如果我们怀疑可能存在不正确的输入数据,例如当 all_users/0 从 JSON 文件读取用户时,这可能会成为问题。因此,谨慎的代码会倾向于崩溃而不是静默地过滤掉不正确的输入,就必须使用更详细的 map 函数:

lists:map(fun(#{user := User, email := Email}) -> {User, Email} end,
          all_users())

与生成器不同,匿名函数会在没有电子邮件地址的用户上崩溃。严格生成器也允许在推导式中使用类似的语义:

[{User, Email} || #{user := User, email := Email} <:- all_users()]

如果模式与列表中的元素不匹配,则此生成器将崩溃(并出现 badmatch 错误)。

提议的严格生成器的运算符是 <:-(用于列表和映射)和 <:=(用于位串),而不是 <-<=。选择此语法是因为 <:-<:= 有点类似于测试两个项是否匹配的 =:= 运算符,同时保持运算符简短且易于键入。两种类型的运算符仅相差一个字符 :,这也使得运算符容易记住,即 “: 表示严格。”

替代设计 #

实现遇到不匹配的输入时崩溃的目标还有许多其他方法:

  1. 将推导式转换为 map 调用,就像上一节的示例一样。

  2. 将可能失败的模式匹配移动到推导式的结果表达式:

    [begin #{user:= User, email := Email} = Usr, {User, Email} end || Usr <- all_users()]

  3. 将可能失败的模式匹配移动到推导式中的过滤器(仍然必须计算为布尔值,这使得此解决方案看起来相当尴尬):

    [{User, Email} || Usr <- all_users(), (#{user := User, email := Email} = Usr) > 0]

  4. 与之前相同,但使用 EEP 12 中提议的绑定器:

    [{User, Email} || Usr <- all_users(), #{user := User, email := Email} = Usr]

这些解决方案中的大多数都比严格的生成器语法更冗长,从而削弱了推导式的简洁性。但还有更严重的问题:

  • 1 不适用于位串推导式,因为没有可用于位串的 map 函数。

  • 2 将使不可能在后续生成器或过滤器中使用模式的子项(本例中的 Usr)。

  • 在大多数这些解决方案中(绝对不是在 3 中,而且在 4 和 2 中也存在争议),代码的意图不明确。

  • 这些技术都不能解决位串的最终不匹配位的问题。

位串生成器的问题在于,与列表和映射不同,位串没有生成器可以迭代的自然“元素”。相反,生成器中的模式指示将位串拆分为哪些部分。这不可避免地会导致位串以一些不匹配模式的位结尾的情况,例如此示例:

[X || <<X:16>> <- <<1,2,3>>]

现有的生成器将跳过这些最终不匹配的位,并且由于向后兼容性的原因,无法更改此行为。只有通过引入一种新型的位串生成器或其他一些会改变现有生成器行为的新语法,才能保证位串生成器会完全消耗其输入。

参考实现 #

当前实现:PR #8625

向后兼容性 #

拟议的新语法不存在向后兼容性问题,因为 <:-<:= 运算符在以前的 Erlang 版本中是无效的语法。

版权 #

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