管道运算符 (|>) 提案进展如何?
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
在发布 babel@7.0.0-beta52 时,我们为 @babel/plugin-proposal-pipeline-operator 引入了一个新的必需配置标志,这对管道运算符来说是一项破坏性变更。为澄清可能的困惑,让我们深入了解管道提案以及为何需要引入此配置选项。
当前状态
管道运算符最初由 Gilbert Garza 引入,旨在"以可读性强、函数式的方式简化链式函数调用"。该运算符源自 F#、Hack、Elm、Elixir 等多种语言,但在引入 JavaScript 时存在两个主要争议点:
-
是否引入占位符及如何引入
-
如何在管道中处理 async/await
占位符问题
首要争议是占位符问题。Kevin Smith 在此议题中首次提出,建议采用 Hack 风格的管道。在 Hack 中,管道的每个右侧都需要占位符,示例如下:
namespace Hack\UserDocumentation\Operators\Pipe\Examples\MapFilterCountPiped;
function piped_example(array<int> $arr): int {
return $arr
|> \array_map($x ==> $x * $x, $$)
|> \array_filter($$, $x ==> $x % 2 == 0)
|> \count($$);
}
var_dump(piped_example(range(1, 10)));
我们基于此概念进行扩展,因为占位符可轻松用于任意表达式,代表前一步骤的返回值。这为管道步骤提供了额外的灵活性和功能。
缺点在于引入新标记的复杂性。当前选择井号(#)作为占位符,尽管仍有争议空间,但任何标记都可能存在多重含义。井号已被私有字段提案使用,且其他所有选项均有不同形式的用途。
异步处理
管道运算符初始提案包含以下 await 语法:
x |> await f
其脱糖(desugar)后等价于:
await f(x)
遗憾的是,用户可能期望另一种脱糖形式:
(await f)(x)
尽管存在反对在管道中处理异步操作的声音,但委员会成员担忧不支持 async/await 的管道运算符。虽然可通过显式语法之外的方式处理返回 Promise 的函数,但这些方法要么过于繁琐,要么需要辅助函数。
提案解决方案
经过讨论,形成了两个主要提案及一个基础最小提案:F# 管道和智能管道。下面解析它们如何解决上述问题。
最小管道提案
该提案涵盖管道运算符的基本功能。最小提案禁止使用 await(完全不涉及异步处理)且不包含占位符。其行为与我们引入配置前的 Babel 插件一致,也是当前管道运算符提案仓库中的规范。它更像是用于对比其他提案优劣的"稻草人"方案,若两个替代方案均无致命缺陷,该提案不太可能被直接采纳。
F# 管道提案
关于占位符问题,F# 管道主张无需引入。在基础提案中,箭头函数可替代占位符的功能,这样既减少新语法量,又基于开发者自 ES2015 以来已熟悉且广泛使用的语法构建。
按照当前规范,箭头函数必须包裹在括号中:
let person = { score: 25 };
let newScore = person.score
|> double
|> (_ => add(7, _))
|> (_ => boundScore(0, 100, _));
社区正在探索是否可能允许箭头函数省略括号,因为括号会带来显著的语法负担。
关于异步处理,F# 管道将 await 视为一元函数:
promise |> await
其等价转换形式为:
await promise
因此可在包含异步操作的函数链中这样使用:
promise
|> await
|> (x => doubleSay(x, ', '))
|> capitalize
|> (x => x + '!')
|> (x => new User.Message(x))
|> (x => stream.write(x))
|> await
|> console.log;
这种对 await 的特殊处理可能也适用于其他一元操作符(如 typeof),但 F# 管道初始版本并不支持。
智能管道(Smart Pipelines)
智能管道将占位符理念推向极致,使其既能管理部分应用也能处理管道中的任意表达式。上文的长链可改写为:
promise
|> await #
|> doubleSay(#, ', ')
|> # || throw new TypeError()
|> capitalize
|> # + '!'
|> new User.Message(#)
|> await stream.write(#)
|> console.log;
智能管道对占位符设有特定规则。若管道步骤中传入的是简单标识符(称为"裸风格"),则无需占位符标记:
x |> a;
x |> f.b;
与 Hack 不同,一元函数在此不需占位符标记。
对于其他表达式(称为"话题风格"),则必须使用占位符(称为"词法话题标记"),否则会抛出早期语法错误:
10 |> # + 1;
promise |> await #;
若表达式包含操作符、括号(含方法调用)、方括号或除标识符与点号外的任何元素,则必须使用话题标记。这避免了未使用话题标记时的隐患和歧义。
因此智能管道以集成化方式解决了异步问题,允许所有表达式(不仅是 await,还包括 typeof、yield 等操作符)嵌入管道。
Babel 的介入角色
当三个提案均完善后,我们意识到仅靠讨论难以化解它们之间的根本矛盾。决定通过开发者在实际代码中的使用反馈来做最终选择。鉴于 Babel 在社区中的定位,我们决定在管道操作符插件中同时实现这三个提案。
由于各提案的解析逻辑存在差异,需要在 @babel/parser(原 babylon)中增加支持,并根据目标提案配置解析器。因此管道操作符插件需引入 "proposal" 选项,用于配置解析器及自身转换逻辑。
此项工作面临紧迫时间线:必须在 babel@7 结束 beta 前完成对 babel 核心、@babel/parser 及管道提案插件的破坏性变更。同时希望插件最终能默认采用被 TC39 接纳的提案,使配置选项自然淘汰。
基于这两点约束,我们决定引入这个必需配置项,强制用户明确项目所用提案。待某个提案成为管道操作符的规范实现后,我们将弃用 "proposal" 选项并默认采用该提案,被否决的提案将在后续主版本中移除。
参与贡献
如果你有兴趣参与管道操作符提案,所有讨论都是公开的,你可以在pipeline operator repository中找到相关内容。也可以查看上次TC39会议中的演示文稿。最后,你可以在Twitter上关注James DiGioia、J. S. Choi或Daniel Ehrenberg。
但最重要的是,当相关工作完成后,请在你的项目中亲自试用管道操作符!我们也在为repl添加配置选项,届时你也可以在那里体验代码。我们需要实际使用反馈和真实代码样本来完善这项功能,非常期待听到你的见解。欢迎通过Twitter@babeljs与我们联系。