Skip to main content

管道 (|>) 提案现在怎么样了?

· 14 min read

随着 babel@7.0.0-beta52 的发布,我们在 @babel/plugin-proposal-pipeline-operator 中引入了一个新的必需配置标志,这对于管道操作符来说是一个破坏性更改。为了消除任何混淆,我们来看看管道提案,以及为什么我们需要引入这个配置选项。

🌐 With the release of babel@7.0.0-beta52, we introduced a new required configuration flag to @babel/plugin-proposal-pipeline-operator, a breaking change for the pipeline operator. To clear up any confusion, let's take a look at the pipeline proposal and why we needed to introduce this configuration option.

当前状态​

🌐 Current Status

管道操作符最初是由 Gilbert Garza 引入 的,它提供了一种干净的语法,用于“以可读、函数式的方式简化链式函数调用”。管道操作符在多种语言中都有根源,包括 F#、Hack、Elm、Elixir 等,但在将这种新语法引入 JavaScript 时有两个主要争议点:

🌐 The pipeline operator was originally introduced by Gilbert Garza, providing a clean syntax for "streamlining chained function calls in a readable, functional manner." The pipeline operator has roots in a number of languages, including F#, Hack, Elm, Elixir, and others, but there were two major points of contention in introducing the new syntax to JavaScript:

  • 是否以及如何引入占位符
  • 在流水线中如何处理 async / await

占位符​

🌐 Placeholders

第一个问题是占位符的问题。这最早是由 Kevin Smith 在 这个问题 中提出的,他建议使用 Hack 风格的流水线。在 Hack 中,每个流水线的右侧都需要一个占位符,就像这个例子一样:

🌐 The first issue was the question of placeholders. This was first raised by Kevin Smith in this issue, where he suggested Hack-style pipelining. In Hack, a placeholder is required for every right-hand side of the pipeline, as in this example:

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)));

我们在这个概念的基础上进行了扩展,因为占位符可以很容易地用于任意表达式,占位符代表前一步返回的值。这为管道步骤提供了额外的灵活性和功能。

🌐 We built on this concept, as a placeholder can easily be used in arbitrary expressions, with the placeholder representing the value returned from the previous step. This affords additional flexibility and power within a pipeline step.

缺点是引入新令牌涉及的复杂性。哈希(#)是目前的选择,虽然这仍然可能被过度讨论,但任何令牌都可能有多重含义。这个哈希也被私有字段提案使用,而且所有其他选项以某种形式都已在用。

🌐 The downside is the complexity involved in introducing a new token. The hash (#) is the current choice, and although this is still open to bikeshedding, any token would potentially have multiple meanings. The hash is also used by the private fields proposal, and all other options are in use in one form or another.

异步 / 等待​

🌐 Async / Await

管道的最初介绍包括了 await 的这种语法:

🌐 The initial introduction of the pipeline included this syntax for await:

JavaScript
x |> await f

将被展开为

🌐 which would desugar to

JavaScript
await f(x)

不幸的是,用户可能会期待这个替代的语法糖展开:

🌐 Unfortunately, users may expect this alternative desugaring:

JavaScript
(await f)(x)

虽然对于在流水线中加入异步处理的想法有人反对,但委员会成员对一个不处理 async/await 的流水线操作符表示担忧。虽然有一些方法可以处理返回 Promise 的函数而不使用显式语法,但它们太繁琐,不实用,或者需要辅助函数。

🌐 While there was pushback on the idea of including async handling in the pipeline at all, committee members expressed concern about a pipeline operator that didn't handle async/await. While there are ways to handle Promise-returning functions without explicit syntax, they are too cumbersome to be useful or require a helper function.

提出的解决方案​

🌐 Proposed Solutions

通过这些讨论,出现了两个提议,以及一个基本的最小提议,用来解决这些问题:F# 管道和智能管道。让我们看看它们是如何解决上面提出的问题的。

🌐 As a result of these discussions, two proposals, along with a base minimal proposal, emerged to resolve them: F# Pipelines and Smart Pipelines. Let's go through how they resolve the problems posed above.

最小化管道​

🌐 Minimal Pipelines

这个提案涵盖了管道操作符的基本功能。最简方案禁止使用 await,所以完全不涉及异步处理,也没有占位符。它的行为与我们引入配置以前的 babel 插件一致,并且是管道操作符提案仓库中当前的规范。它更像是一个试验性的方案,用来比较其他提案的优缺点,不太可能在存在两种方案中致命缺陷的情况下直接被接受。

🌐 This proposal covers the basic functionality of the pipeline operator. The minimal proposal bans await, so there's no async handling involved at all, and includes no placeholders. It matches the behavior of the babel plugin before we introduced the configuration and is the current specification in the pipeline operator proposal repository. It functions more as a straw man, to compare the benefits and tradeoffs of other the proposals, and is unlikely to be accepted as-is without lethal defects in both of the alternatives.

F# 流水线​

🌐 F# Pipelines

关于占位符的问题,F# Pipelines 认为它们不是必需的。在基础提案中,箭头函数承担了占位符的功能,所需的新语法更少,并且基于开发者早已熟悉并自 ES2015 起就一直在使用的语法。

🌐 On the question of placeholders, F# Pipelines argue they're not needed. In the base proposal, arrow functions fill the area placeholders fill, requiring less new syntax and building on a syntax developers are already familiar with and have been using since ES2015.

按目前的配置,箭头函数需要用括号包起来:

🌐 As currently specced, arrow functions are required to be wrapped in parentheses:

JavaScript
let person = { score: 25 };

let newScore = person.score
|> double
|> (_ => add(7, _))
|> (_ => boundScore(0, 100, _));

目前正在探索是否可行让箭头函数在不使用括号的情况下也能使用,因为它们的语法负担相当大。

🌐 Exploration is underway to determine whether it would be feasible to enable arrow functions to be used without parentheses, as they are a significant syntactical burden.

关于异步的问题,F# 管道对 await 的处理类似于一元函数:

🌐 On the question of async, F# Pipelines treat await similar to a unary function:

JavaScript
promise |> await

这将被转换成:

🌐 This would desugar to:

JavaScript
await promise

因此可以在更大函数链的中间与 async 一起使用:

🌐 and can thus be used in the middle of larger function chains with async:

JavaScript
promise
|> await
|> (x => doubleSay(x, ', '))
|> capitalize
|> (x => x + '!')
|> (x => new User.Message(x))
|> (x => stream.write(x))
|> await
|> console.log;

await 的特殊处理可能会使其他一元运算符也能以类似方式使用(例如 typeof),但是 F# 的管道最初并不支持它们。

🌐 The special casing of await could potentially enable other unary operators to be used similarly (e.g. typeof), but the F# pipelines don't support them initially.

智能管道​

🌐 Smart Pipelines

Smart Pipelines 将占位符的概念发挥到了极致,使其能够在管道中管理部分应用和任意表达式。上面的长链可以这样写:

🌐 Smart Pipelines takes the idea of the placeholder to its logical conclusion, enabling it to manage partial application as well as arbitrary expressions in a pipeline. The above long chain would be written thus:

JavaScript
promise
|> await #
|> doubleSay(#, ', ')
|> # || throw new TypeError()
|> capitalize
|> # + '!'
|> new User.Message(#)
|> await stream.write(#)
|> console.log;

智能管道对占位符有一些规则。如果在管道的某个步骤中提供了一个裸标识符,则无需令牌,这种方式称为“裸风格”:

🌐 Smart Pipelines have a few rules for the placeholder. If a bare identifier is provided to a step in the pipeline, no token is necessary, called "bare style":

JavaScript
x |> a;
x |> f.b;

不像 Hack,一元函数不需要占位符。

🌐 Unlike Hack, unary functions don't require a placeholder token.

对于其他表达,需要一个占位符(称为“词汇主题标记”),如果它没有包含在"主题风格"中,代码会提前抛出语法错误:

🌐 For other expressions, a placeholder (called a "lexical topic token") is required, and the code will throw an early SyntaxError if it is not included in "topic style":

JavaScript
10 |> # + 1;
promise |> await #;

如果有任何运算符、括号(包括方法调用用的括号)、方括号,或者除了标识符和点号之外的任何东西,那么就需要使用主题标记。这样可以避免踩雷,并且在不使用主题标记时消除歧义。

🌐 If there are any operators, parentheses (including for method calls), brackets, or anything other than identifiers and dot punctuators, then a topic token is necessary. This avoids footguns and eliminate ambiguity when not using a topic token.

因此,智能管道以一种综合的方式解决了异步问题,允许将所有可能的表达式嵌入管道中;不仅是 await,还有 typeof、yield 以及其他所需的操作符。

🌐 Smart pipelines thus resolve the issue of async in an integrative way, allowing all possible expressions to be embedded in a pipeline; not only await, but also typeof, yield, and another other operator desired.

Babel的介入​

🌐 Where Babel Comes In

一旦三个提案都具体化后,我们意识到讨论和辩论不太可能解决它们之间固有的矛盾。我们决定,最好的决策方式是让开发者提供反馈,通过在实际代码中使用这些提案。鉴于 Babel 在社区中的作用,我们决定把上述三个提案都引入管道操作符插件中。

🌐 Once all three proposals were fleshed out, we realized discussion and debate were unlikely to resolve the inherent tension between them. We decided the best way to make a decision would be with the feedback of developers, using the proposals in real code. Given Babel's role in the community, we decided to introduce all three of the above proposals into the pipeline operator plugin.

因为这些提案的解析略有不同,所以需要为 @babel/parser(以前的 babylon)添加支持,并且解析器需要根据当前的解析目标选择配置。因此,管道操作符插件本身需要 "proposal" 选项,既用于配置 babylon,也用于它自己的转换。

🌐 Because these proposals parse slightly differently, support needs to be added to @babel/parser (formerly babylon), and the parser needs to be configured based on which proposal is its current parse target. The pipeline operator plugin itself thus needs the "proposal" option, both to configure babylon as well as its own transformation.

我们在一个紧迫的时间表下进行这项工作,因为我们需要在 babel@7 结束测试版之前,对 babel、@babel/parser 以及管道提案插件引入任何破坏性更改。我们也希望这个插件最终能够默认使用被采纳的提案,这样配置选项就会变得多余。

🌐 We were working under a compressed timeline for this, as we needed to introduce any breaking changes to babel, @babel/parser, as well as the pipeline proposal plugin before babel@7 left beta. We also wanted the plugin to be able to eventually default to whichever of the proposals get accepted, so the configuration option becomes obsolete.

鉴于这两个限制,我们选择引入这个新的配置选项并将其设为必填,这样用户就必须决定他们想在项目中使用哪个提案。一旦某个提案被接受为管道操作符的规范行为,我们就会弃用 "proposal" 选项,并将默认值设置为被接受的提案,而被拒绝的提案将在下一个主要版本中移除。

🌐 Given these two constraints, we opted to introduce this new configuration option and make it required, forcing users to decide which proposal they want to use in their project. Once a particular proposal gets accepted as the canonical behavior of the pipeline operator, we'll deprecate the "proposal" option and make the default whichever gets accepted, and the rejected proposals will be removed in the following major version.

参与进来​

🌐 Get Involved

如果你对参与管道提案感兴趣,所有讨论都是公开的,你可以在 pipeline operator 仓库 找到它们。你也可以看看 上次 TC39 会议的演示。最后,你可以在 Twitter 上找到 James DiGioia、J. S. Choi 或 Daniel Ehrenberg。

🌐 If you're interested in getting involved in the pipeline proposal, all the conversations are public and you find them in the pipeline operator repository. You can also check out the presentation from the last TC39 meeting. Lastly, you can find James DiGioia, J. S. Choi, or Daniel Ehrenberg on Twitter.

但最重要的是,一旦工作完成,尝试在你自己的项目中使用管道操作符吧!我们也在努力为 repl 添加选项,这样你也可以在那里试用代码。为了让这些功能真正有用,我们需要反馈和真实的代码,所以我们非常想听听你的意见。发推 @babeljs (https://twitter.com/babeljs) 告诉我们吧。

🌐 But most importantly, once the work is complete, try out the pipeline operator in your own projects! We're also working on adding options to the repl, so you'll be able to try out the code there as well. We need feedback and real code in order for this to be useful, so we'd love to hear from you. Tweet us at @babeljs to let us know.