关于前端:精读pipe-operator-for-JavaScript

46次阅读

共计 4251 个字符,预计需要花费 11 分钟才能阅读完成。

Pipe Operator (|>) for JavaScript 提案给 js 减少了 Pipe 语法,这次联合 A pipe operator for JavaScript: introduction and use cases 文章一起深刻理解这个提案。

概述

Pipe 语法能够将函数调用按程序打平。如下方函数,存在三层嵌套,但咱们解读时须要由内而外浏览,因为调用程序是由内而外的:

const y = h(g(f(x)))

Pipe 能够将其转化为失常程序:

const y = x |> f(%) |> g(%) |> h(%)

Pipe 语法有两种格调,别离来自 Microsoft 的 F#) 与 Facebook 的 Hack)。

之所以介绍这两个,是因为 js 提案首先要决定“借鉴”哪种格调。js 提案最终采纳了 Hack 格调,因而咱们最好把 F# 与 Hack 的格调都理解一下,并对其优劣做一个比照,能力知其所以然。

Hack Pipe 语法

Hack 语法绝对冗余,在 Pipe 时应用 % 传递后果:

'123.45' |> Number(%)

这个 % 能够用在任何中央,基本上原生 js 语法都反对:

value |> someFunction(1, %, 3) // function calls
value |> %.someMethod() // method call
value |> % + 1 // operator
value |> [%, 'b', 'c'] // Array literal
value |> {someProp: %} // object literal
value |> await % // awaiting a Promise
value |> (yield %) // yielding a generator value

F# Pipe 语法

F# 语法绝对精简,默认不应用额定符号:

'123.45' |> Number

但在须要显式申明参数时,为了解决上一个 Pipe 后果符号从哪来的问题,写起来反而更为简单:

2 |> $ => add2(1, $)

await 关键字 – Hack 优

F# 在 await yield 时须要非凡语法反对,而 Hack 能够天然的应用 js 内置关键字。

// Hack
value |> await %
// F#
value |> await

F# 代码看上去很精简,但实际上付出了昂扬的代价 – await 是一个仅在 Pipe 语法存在的关键字,而非一般 await 关键字。如果不作为关键字解决,执行逻辑就变成了 await(value) 而不是 await value

解构 – F# 优

正因为 F# 繁琐的变量申明,反而使得在应答解构场景时得心应手:

// F#
value |> ({a, b}) => someFunction(a, b)
// Hack
value |> someFunction(%.a, %.b)

Hack 也不是没有解构伎俩,只是比拟繁琐。要么应用立刻调用函数表达式 IIFE:

value |> (({a, b}) => someFunction(a, b))(%)

要么应用 do 关键字:

value |> do {const { a, b} = %; someFunction(a, b) }

但 Hack 虽败犹荣,因为解决办法都应用了 js 原生提供的语法,所以反而体现出与 js 已有生态亲和性更强,而 F# 之所以能优雅解决,全都归功于借鉴的语法,这些语法尽管甜,但割裂了 js 生态,这是 F# like 提案被放弃的重要起因之一。

潜在改良计划

尽管抉择了 Hack 格调,但 F# 与 Hack 各有优劣,所以列了几点优化计划。

利用 Partial Application Syntax 提案升高 F# 传参复杂度

F# 被诟病的一个起因是传参不如 Hack 简略:

// Hack
2 |> add2(1, %)
// F#
2 |> $ => add2(1, $)

但如果利用处于 stage1 的提案 Partial Application Syntax 能够很好的解决问题。

这里就要做一个小插曲了。js 对柯里化没有原生反对,但 Partial Application Syntax 提案解决了这个问题,语法如下:

const add = (x, y) => x + y;
const addOne = add~(1, ?);
addOne(2); // 3

即利用 fn~(?, arg) 的语法,将任意函数柯里化。这个个性解决 F# 传参简单问题几乎绝配,因为 F# 的每一个 Pipe 都要求是一个函数,咱们能够将要传参的中央记为 ?, 这样返回值还是一个函数,完满合乎 F# 的语法:

// F#
2 |> add~(1, ?)

下面的例子拆开看就是:

const addOne = add~(1, ?)
2 |> addOne

想法很美妙,但 Partial Application Syntax 得先落地。

交融 F# 与 Hack 语法

在简略状况下应用 F#,须要利用 % 传参时应用 Hack 语法,两者混合在一起写就是:

const resultArray = inputArray
  |> filter(%, str => str.length >= 0) // Hack
  |> map(%, str => '['+str+']') // Hack
  |> console.log // F#

不过这个 提案 被废除了。

发明一个新的操作符

如果用 |> 示意 Hack 语法,用 |>> 示意 F# 语法呢?

const resultArray = inputArray
  |> filter(%, str => str.length >= 0) // Hack
  |> map(%, str => '['+str+']') // Hack
  |>> console.log // F#

也是看上去很美妙,但这个个性连提案都还没有。

如何用现有语法模仿 Pipe

即使没有 Pipe Operator (|>) for JavaScript 提案,也能够利用 js 现有语法模仿 Pipe 成果,以下是几种计划。

Function.pipe()

利用自定义函数结构 pipe 办法,该语法与 F# 比拟像:

const resultSet = Function.pipe(
  inputSet,
  $ => filter($, x => x >= 0)
  $ => map($, x => x * 2)
  $ => new Set($)
)

毛病是不反对 await,且存在额定函数调用。

应用两头变量

说白了就是把 Pipe 过程拆开,一步步来写:

const filtered = filter(inputSet, x => x >= 0)
const mapped = map(filtered, x => x * 2)
const resultSet = new Set(mapped)

没什么大问题,就是比拟冗余,原本可能一行能解决的问题变成了三行,而且还申明了三个两头变量。

复用变量

革新一下,将两头变量变成复用的:

let $ = inputSet
$ = filter($, x => x >= 0)
$ = map($, x => x * 2)
const resultSet = new Set($)

这样做可能存在变量净化,可应用 IIFE 解决。

精读

Pipe Operator 语义价值非常明显,甚至能够扭转编程的思维形式,在串行解决数据时十分重要,因而命令行场景十分常见,如:

cat "somefile.txt" | echo

因为命令行就是典型的输入输出场景,而且大部分都是单输出、单输入。

在一般代码场景,特地是解决数据时也须要这个个性,大部分具备抽象思维的代码都进行了各种类型的管道形象,比方:

const newValue = pipe(
  value,
  doSomething1,
  doSomething2,
  doSomething3
)

如果 Pipe Operator (|>) for JavaScript 提案通过,咱们就不须要任何库实现 pipe 动作,能够间接写成:

const newValue = value |> doSomething1(%) |> doSomething2(%) |> doSomething3(%)

这等价于:

const newValue = doSomething3(doSomething2(doSomething1(value)))

显然,利用 pipe 个性书写解决流程更为直观,执行逻辑与浏览逻辑是统一的。

实现 pipe 函数

即使没有 Pipe Operator (|>) for JavaScript 提案,咱们也能够一行实现 pipe 函数:

const pipe = (...args) => args.reduce((acc, el) => el(acc))

但要实现 Hack 参数格调是不可能的,顶多实现 F# 参数格调。

js 实现 pipe 语法的思考

从 提案 记录来看,F# 失败有三个起因:

  • 内存性能问题。
  • await 非凡语法。
  • 割裂 js 生态。

其中割裂 js 生态是指因 F# 语法的特殊性,如果有太多库依照其语法实现性能,可能导致无奈被非 Pipe 语法场景所复用。

甚至还有局部成员拥护 隐性编程 (Tacit programming),以及柯里化提案 Partial Application Syntax,这些会使 js 反对的编程格调与当初差别过大。

看来处于鄙视链顶端的编程格调在 js 是否反对不是能不能的问题,而是想不想的问题。

pipe 语法的弊病

上面是一般 setState 语法:

setState(state => ({
  ...state,
  value: 123
}))

如果改为 immer 写法如下:

setState(produce(draft => draft.value = 123))

得益于 ts 类型主动推导,在内层 produce 里就曾经晓得 value 是字符串类型,此时如果输出字符串会报错,而如果其在另一个上下文的 setState 内,类型也会随着上下文的变动而变动。

但如果写成 pipe 模式:

produce(draft => draft.value = 123) |> setState

因为先思考的是如何批改数据,此时还不晓得前面的 pipe 流程是什么,所以 draft 的类型无奈确定。所以 pipe 语法仅实用于固定类型的数据处理流程。

总结

pipe 直译为管道,潜在含意是“数据像流水线一样被解决”,也能够形象了解为每个函数就是一个不同的管道,显然下一个管道要解决上一个管道的数据,并将后果输入到下一个管道作为输出。

适合的管道数量与体积决定了一条生产线是否高效,过多的管道类型反而会使流水线零散而芜杂,过少的管道会让流水线轻便不易拓展,这是工作中最大的考验。

探讨地址是:精读《pipe operator for JavaScript》· Issue #395 · dt-fe/weekly

如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。

关注 前端精读微信公众号

<img width=200 src=”https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg”>

版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)

正文完
 0