Closed hax closed 4 years ago
现在 pipe operator 和 bind operator 相比,在使用上的话,不再需要 this,而且还可以兼容现在像 lodash 这种扩展库,看起来更加有前景
首先如果看 underscore/lodash 的历史,可以发现其早期其实是以 this 方式运作的,如 _(a).first()。之所以后来改用所谓fp形式(_.first(a)),并不是因为大家真的想用fp [1],而是wrapper方式有很多难解的缺陷(比如性能,比如unwrap需要打断链式调用)。但换了所谓fp形式之后,也有缺陷,就是压根没法链式调用了。所以大家希望要上 pipe op。
this
_(a).first()
_.first(a)
[1] 至少当初不是的,或者说得难听点,很多人其实是叶公好龙,并不是想用真fp——比方说ramda这样的库其实大多数人是接受不了的。
从某种程度上说,underscore/lodash 之所以出现 fp 形式是因为 bind op 提案停滞了,大家迫不得已找其他出路,但也有问题,于是大家又想着解决这个问题,就推 pipe op,但是忘记了(或者否定了)其实本来 bind op 就可以解决问题。
bind op为什么会停滞,当然是因为 bind op 本身有一些问题,这也是为什么我要重新设计 extensions proposal 来取代它。这里暂不展开。
但其实pipe op一样问题严重,在 stage 1 上停滞不前。bind op虽然是stage 0,但当时的stage 0其实和stage 1比较类似,尤其在生态上也是有 babel 支持的。
我并不是想否定pipe和fp。但我认为,相对来说,extensions 是和 js 语言的现有特性合作得更好的,且 pipe(和其他一些fp需求)是可以建立在 extensions 之上的,比如我们其实不一定需要 x |> f,可以用 x::pipe(f),也就是 pipe 完全可以是不需要语法,而用 userland 库来解决。即使真要标准化,也可以只标准化库,这给到委员会更大的空间和自由,更小的压力。
x |> f
x::pipe(f)
下面我简要阐述下 pipe 和 extensions/bind 的区别:
extensions/bind 毫无争议其左侧是作为 this 参数的。而 pipe 是有争议的,到底左侧是作为 this (比如有人可能想写 v |> Object.prototype.toString)还是唯一一个参数(常见的 F# style),还是第一个参数(Elixer style,允许直接用现有的lodash库 document.all |> lodash.map(e => ...) ),或者最后一个参数(比如 map 函数可能并不是像 lodash 那样签名为 (Iterable<T>, T => U) => U,而是和许多函数式语言一样顺序的 (T => U, Iterable<T>) => U,或者任意位置(使用 # topic 的smart style —— x |> f(1, #))。无论pipe op语法最后选择哪一个语义都会造成另一部分人的惊讶(比如 let m = o.m; x |> m(1) 到底表示什么),并让那些use cases变得相对写起来不爽。
v |> Object.prototype.toString
document.all |> lodash.map(e => ...)
(Iterable<T>, T => U) => U
(T => U, Iterable<T>) => U
#
x |> f(1, #)
let m = o.m; x |> m(1)
pipe 的运算符优先级和结合性是比较低的,比如 x |> o.f() 肯定会被理解为 x |> (o.f()) 而不是 (x |> o).f(),而且 pipe 调用是没有括号的(如 x |> f),因此和既有的OO风格的api(所有JS标准库都是OO风格的)合用会比较别扭,特别是已经被设计为 Fluent interface 的 api,如 (x.a().b() |> c |> d).a().b(),注意括号的不一致性严重破坏了流畅感。而 extensions/bind 其运算符优先级和结合性应该和 . 一致,这样就是 x.a().b()::c()::d().a().b() 。【注意当前 bind op的::优先级比 . 要低,也就是采用了靠近 pipe op 的优先级,这正是 bind op 草案的一大缺陷,也是造成不少负反馈的原因之一。】
x |> o.f()
x |> (o.f())
(x |> o).f()
(x.a().b() |> c |> d).a().b()
.
x.a().b()::c()::d().a().b()
::
extensions/bind 应该可以比 pipe op 更简单的就地复用现有的 JS api。比如按照我的 extensions 草案,你可以写:
const nullProto = Object.create(null) ... // 支持解构语法 const Object::{hasOwnProperty, isPrototypeOf} = Object.prototype
for (const k in obj) { if (obj::hasOwnProperty(k) && nullProto::isPrototypeOf(obj[k]) ) { ... } }
而用 pipe 的话,就麻烦点: ```js const nullProto = Object.create(null) ... // 假设采用 F# style 的 pipe op const hasOwnProperty = k => thisArg => Object.prototype.hasOwnProperty.call(thisArg, k) const isPrototypeOf = v => thisArg => Object.prototype.isPrototypeOf.call(thisArg, v) for (const k in obj) { if ((obj |> hasOwnProperty(k)) && (nullProto |> isPrototypeOf(obj[k])) ) { ... } }
以上。想到再补充。
讨论移步 https://github.com/JSCIG/es-discuss/issues/3
首先如果看 underscore/lodash 的历史,可以发现其早期其实是以
this
方式运作的,如_(a).first()
。之所以后来改用所谓fp形式(_.first(a)
),并不是因为大家真的想用fp [1],而是wrapper方式有很多难解的缺陷(比如性能,比如unwrap需要打断链式调用)。但换了所谓fp形式之后,也有缺陷,就是压根没法链式调用了。所以大家希望要上 pipe op。[1] 至少当初不是的,或者说得难听点,很多人其实是叶公好龙,并不是想用真fp——比方说ramda这样的库其实大多数人是接受不了的。
从某种程度上说,underscore/lodash 之所以出现 fp 形式是因为 bind op 提案停滞了,大家迫不得已找其他出路,但也有问题,于是大家又想着解决这个问题,就推 pipe op,但是忘记了(或者否定了)其实本来 bind op 就可以解决问题。
bind op为什么会停滞,当然是因为 bind op 本身有一些问题,这也是为什么我要重新设计 extensions proposal 来取代它。这里暂不展开。
但其实pipe op一样问题严重,在 stage 1 上停滞不前。bind op虽然是stage 0,但当时的stage 0其实和stage 1比较类似,尤其在生态上也是有 babel 支持的。
我并不是想否定pipe和fp。但我认为,相对来说,extensions 是和 js 语言的现有特性合作得更好的,且 pipe(和其他一些fp需求)是可以建立在 extensions 之上的,比如我们其实不一定需要
x |> f
,可以用x::pipe(f)
,也就是 pipe 完全可以是不需要语法,而用 userland 库来解决。即使真要标准化,也可以只标准化库,这给到委员会更大的空间和自由,更小的压力。下面我简要阐述下 pipe 和 extensions/bind 的区别:
extensions/bind 毫无争议其左侧是作为
this
参数的。而 pipe 是有争议的,到底左侧是作为this
(比如有人可能想写v |> Object.prototype.toString
)还是唯一一个参数(常见的 F# style),还是第一个参数(Elixer style,允许直接用现有的lodash库document.all |> lodash.map(e => ...)
),或者最后一个参数(比如 map 函数可能并不是像 lodash 那样签名为(Iterable<T>, T => U) => U
,而是和许多函数式语言一样顺序的(T => U, Iterable<T>) => U
,或者任意位置(使用#
topic 的smart style ——x |> f(1, #)
)。无论pipe op语法最后选择哪一个语义都会造成另一部分人的惊讶(比如let m = o.m; x |> m(1)
到底表示什么),并让那些use cases变得相对写起来不爽。pipe 的运算符优先级和结合性是比较低的,比如
x |> o.f()
肯定会被理解为x |> (o.f())
而不是(x |> o).f()
,而且 pipe 调用是没有括号的(如x |> f
),因此和既有的OO风格的api(所有JS标准库都是OO风格的)合用会比较别扭,特别是已经被设计为 Fluent interface 的 api,如(x.a().b() |> c |> d).a().b()
,注意括号的不一致性严重破坏了流畅感。而 extensions/bind 其运算符优先级和结合性应该和.
一致,这样就是x.a().b()::c()::d().a().b()
。【注意当前 bind op的::
优先级比.
要低,也就是采用了靠近 pipe op 的优先级,这正是 bind op 草案的一大缺陷,也是造成不少负反馈的原因之一。】extensions/bind 应该可以比 pipe op 更简单的就地复用现有的 JS api。比如按照我的 extensions 草案,你可以写:
for (const k in obj) { if (obj::hasOwnProperty(k) && nullProto::isPrototypeOf(obj[k]) ) { ... } }
以上。想到再补充。