Replies: 35 comments 6 replies
-
// 假设采用 F# style 的 pipe op
for (const k in obj) {
if ((obj |> o => o.hasOwnProperty(k))
&& (nullProto |> o => o.isPrototypeOf(obj[k]))
) {
...
}
} 我觉得作为被 this 坑过多次的用户,都会写这种吧…… |
Beta Was this translation helpful? Give feedback.
-
为什么接受不了?除了少部分函数类型标注不完善,如果配合用 R.pipe,没啥不好的啊。 |
Beta Was this translation helpful? Give feedback.
-
关于参数位置,支持 lambda 的话,这代码可读性也还不错啊。 const newScore = person.score
|> double
|> (_ => add(7, _))
|> (_ => boundScore(0, 100, _)); |
Beta Was this translation helpful? Give feedback.
-
对于F# style你是总可以写
以上这些决定了你要代码可读性的话,就得写成你第二个例子的样子,但显然牺牲了代码的紧凑性,和真fp语言比起来,语法噪音过高。 这大概也是为什么在mozilla做的调研当中,其实比较多人选择了 smart style( 当然,这些都是语法上的问题,每个人的看法可能不一样,尤其是有些程序员对语法不敏感,所以觉得没什么。但我认为,对于语法争议,必须要考虑语法敏感度高的群体(对于敏感度低的群体,反正什么语法他都接受——比如 此外,需要注意到adhoc的arrow functions只能用于简单情况。在我上面的例子里,一个重要差别是null prototype的对象上是没有 hasOwnProperty 的,所以简单的 这是为什么说 extensions/bind 「可以比 pipe op 更简单的就地复用现有的 JS api」。 |
Beta Was this translation helpful? Give feedback.
-
我指的是,很多人嘴里说用fp其实只是用一点 map/reduce 并不是在整体风格上采用fp,那么真让他大量用 ramda 实际上是不能接受的。 moz的调研其实比较能说明一些问题。我对他们调研的解读是:moz的调研因为并不限于「真·fp爱好者」。所以选择topic style(smart style)的人多一点(考虑原本OO风的语言比如Hack,用的也是topic style,不是没有道理的)。 结果就造成这样的矛盾:
比较起来,(我认为)还是基于userland库的形式更好。爱好「真·fp」的人,和爱好「OO」的人,还有只是想要链式调用的人,都能满意。比如对于fp来说: x::pipe(f)
x::pipe(f(a))
x::pipe(f, [a, b, $]) // 库也完全可以支持 partial application,而且这个形式完全没有性能问题。
// 简单的使用 arrow function 的形式
const newScore = person.score::pipe(
double,
_ => add(7, _),
_ => boundScore(0, 100, _)
);
// 使用库内建的 partial 形式
// 性能也会略好,v8优化比较牛逼,在arrow functions上估计不明显,
// 但如果用单独库写的partial,或者如果用嵌入式引擎,就会有差异了
const newScore = person.score::pipe(
double, [$],
add, [7, $],
boundScore, [0, 100, $],
);
// 我们甚至还能直接支持rest
[a, b]::pipe(f, [c, ...$]) // 相当于 f(c, a, b) |
Beta Was this translation helpful? Give feedback.
-
我们 rx 和 ramda 用户倒是很想要这个 F# style 的 pipe,没有 smart style 那么多魔法。 习惯了 fp 的用户每次用 this 都胆战心惊。 比如 (foo.success? bar.ok: bar.error)(message) 是不行的,但是 (foo.success? resolve: reject)(message) 是可以的。还有在 pipe 中无法用 |
Beta Was this translation helpful? Give feedback.
-
const newScore = person.score::pipe(
double,
_ => add(7, _),
_ => boundScore(0, 100, _)
); 这个有个缺点,就是写类型时候要疯狂写重载。 |
Beta Was this translation helpful? Give feedback.
-
这就是我说的问题。不同用户群体的需求和偏好是矛盾的。单独为某一个群体提供更好的特性也是行的,但是最好是能兼顾所有群体,否则js会逐渐分裂成几个不同的子集语言。
|
Beta Was this translation helpful? Give feedback.
-
你是说pipe这个工具函数的类型? 自动推导应该是没有问题的。就算现在有问题,你要相信TS,类型推导能力是会越来越强的。 |
Beta Was this translation helpful? Give feedback.
-
const newScore = person.score::pipe(
double,
_ => add(7, _),
_ => boundScore(0, 100, _)
); 想了想,你这和 const newScore = pipe(person.score)(
double,
_ => add(7, _),
_ => boundScore(0, 100, _)
); 没差啊。 从这个角度看,pipe 用普通的函数还是用 extensions,实际上没啥本质区别 |
Beta Was this translation helpful? Give feedback.
-
有区别啊。你可以链式了啊。本来要pipeline op的原因就是要 这里的 |
Beta Was this translation helpful? Give feedback.
-
v.method()::pipe(f1).value::pipe(f2)::some_other_extension_method() 我现在一般是写 pipe(
()=>v.method(),
f1,
x=>x.value,
f2,
x=>x.some_other_extension_method()
)() 老实说,我感觉两者的语法噪音都不小…… |
Beta Was this translation helpful? Give feedback.
-
前者已经基本上没有语法噪音了(除了一个新的符号 展开对比看就比较清楚 v
.method()
::pipe(f1)
.value
::pipe(f2)
::some_other_ext_method() |
Beta Was this translation helpful? Give feedback.
-
pipe 和 x=>x都是四个字符(逃) |
Beta Was this translation helpful? Give feedback.
-
正是如此。完整来说应该叫 static extension methods and accessors。
就和一般声明的作用域是一样的。 {
const *::x = ...
obj::x() // ok
}
obj::x() // reference error 不过设计上,和一般的binding有几个小区别: 第一,和一般的binding在分离的命名空间里,所以不会互相干扰。 const *::x = ...
obj::x() // ok
x // reference error 这是为了符合方法的体验,老的 bind op 下面的代码就挂了。 const *::max = function () {
return Math.max(...this)
}
let max = [1, 2, 3]::max()
max // 3 第二,只有 const 没有 let,因为不允许重新定义。 第三,不允许 shadow。 const *::x = ...
{
const *::x = ... // syntax error
} 但下面这种不产生shadow的是可以的: if (...) {
const *::x = ...
} else {
const *::x = ...
} 这个限制是因为shadow对于实际开发弊大于利。而且禁止shadow可以避免在加入类型限定(可能的follow-on proposal)之后产生理解负担。 const *::max = function () { return Math.max(...this) }
let a = [3,2,1]
{
// 程序员想针对 Range 做个优化
const Range::max = function () {
// 很 naive 的实现,暂时忽略了 step 为负数的情况
return this.start > this.end ? this.start: this.end - 1
}
range(1, 5)::max() // expect 4
a::max() // expect 3, actually throw TypeError because a is not a Range
} 实际上上面这个例子就算不shadow也是不行的。 const *::max = function () { return Math.max(...this) }
// 程序员想针对 Range 做个优化
const Range::max = function () { // syntax error,重复定义 该需求可以用 first-class protocol 提案配合 extensions 达成。 protocol Iterable {
...
max() { return Math.max(...this) }
...
}
class Range {
implements Iterable {
/*override*/ max() { ... }
}
}
const *::{max} = Iterable
let r = range(1, 5)
let a = [3, 2, 1]
r::max() // 4
a::max() // 3 |
Beta Was this translation helpful? Give feedback.
-
为什么要单独声明空间,普通的值声明空间不就好了,在实现上可能会更容易,polyfill 也简单。 |
Beta Was this translation helpful? Give feedback.
-
前面的例子其实已经说明了。我补充说明一下: 对于非FP的人来说,写 要知道像 lodash 这样的库,如果你看名字,基本上把大量的常用单词都用了,对于写代码的命名体验就会很差了。linter在这个事情上并没有什么卵用。 这个提案extensions的出发点是 extensions methods,而不是FP(尽管可以用来解决许多FP的问题),所以不能只从FP的角度考虑。 我理解对于FP的人来说,经常要compose函数,所以希望都在命名空间里。如果真的需要,就再导入一下 故我觉得这算是一个比较好的tradeoff。 |
Beta Was this translation helpful? Give feedback.
-
单独命名空间这个事情,我比较不喜欢。现在普通值有一个命名空间、export record 一个命名空间、装饰器单独有一个命名空间、ts / flow 的类型系统一个命名空间、这里又打算弄一个命名空间进来。 而且在 import / const declaration 上再扩展这么多东西,另加这么多命名空间,对需要处理 AST 的工具作者来说也不友好吧。 |
Beta Was this translation helpful? Give feedback.
-
所以应该趁着 decorator 还在 stage 2 扔掉 static decorators 把它回退回 legacy decorators(不是 请教一下:这种单独的命名空间应该怎么转译呢?感觉现在 proposal-decorators 的人也在回避这个问题,只提到了一个 |
Beta Was this translation helpful? Give feedback.
-
Decorator 提案的说法看起来是希望直接 ship 源码(也就是带上 node_modules 里的代码一起转换) 或者社区约定好一种转译方法,大家都来支持,类似 __esModule 属性。 |
Beta Was this translation helpful? Give feedback.
-
如果直接 ship 源码的话……那像 Vue 这样把「只加一个 script tag 就能用」作为卖点的不就完了? |
Beta Was this translation helpful? Give feedback.
-
@Alan-Liang 这和我们今天在 npm 上 ship ES Module 是类似的。同时,有些库也会直接发布未转译的 JSX。(TypeScript 也包含一个选项可以保留 JSX 至于 Vue,它现在有官方在用装饰器语法吗?🤔 |
Beta Was this translation helpful? Give feedback.
-
既然已经有了那么多个命名空间,多一个也没啥……😛 从导入的形式上说,这个东西类似于
我理解。但是,它和 type、private declaration、static decorator (export record 是指啥?)有一个重大不同。就是它本身并不是一个完全不同的东西,也不存在导出!你导出的还是普通的对象和函数。 只有用的时候,你可以决定,是否要当成 extensions 用(从而不污染普通的命名空间)。 所以 从这点来说,它不像 static decorator 或 private declaration那样是一个异质物(并且不是 first-class value,不能当成值传来传去或者被返回)。它仍然是一个普通的东西,只不过是以一种特殊方式使用而已。
@Alan-Liang 综上,实际上extensions确实可以不用单独的命名空间,就像原来的 bind operator 一样。这个设计是我反复考虑了 extensions 的使用场景和用户对于 extensions 使用体验的期待,才添加的。 |
Beta Was this translation helpful? Give feedback.
-
export { useState } from 'react'
useState()
// Cannot find name 'useState'. |
Beta Was this translation helpful? Give feedback.
-
哦,那 export record 的目的就是为了再导出,尤其是用于入口js文件,选择性将接口导出给外部(有点像facade模式),所以通常里面是一堆 export from,并没有其他代码。 从这个角度说,extensions 在这点上和 export record 是类似的,都是特殊用途。export record 的特殊用途是「转手」,所以隐藏;extensions 的特殊用途是「借用」,所以隐藏。 |
Beta Was this translation helpful? Give feedback.
-
tc39/proposal-pipeline-operator#170 这个 issue 因为吵得太凶所以被主动按照 CoC 关掉了。 抛开情绪因素,我认为这个 issue 也再次说明了几个问题:
所以这个 issue 的冲突再一次说明了,强行上 pipeline op ,是行不通的。 |
Beta Was this translation helpful? Give feedback.
-
关于命名空间经过深入阅读和多日思考,对命名空间的问题,从不理解到慢慢领会了,说一下我的想法。 case 1:共享变量命名空间这应该是最小程度的提案了,如果对它有不满,主要可能是两方面:
① 其实目前的库的命名多存在问题这一点,应当靠各大库把 case 2:引入独立命名空间假如上述问题都真实存在,即 ② 由于 ① 中已经说明了命名冲突这一半原因实际上很难成立,因此无论另一半原因是否成立,整体都不太会成立。命名冲突问题唯一真正成立的,就是 accessor 其实贺老说的没错,独立命名空间乍一听令人惊骇,但实际上 js 中独立命名空间早就存在了,比如 那么,有没有可能借力,从而尽可能避免全新开辟战线造成的争议呢?比如: #toJSON () { return JSON.stringify(this); } }
const json = value.#toJSON(); get #json () { return JSON.stringify(this); } }
const json = value.#json; 这样也与目前提案“ 当然,我看到现在已经有在 class 间外部共享的 ::toJSON () { return JSON.stringify(this); } }
const json = value::toJSON(); get ::json () { return JSON.stringify(this); } }
const json = value::json; 这样除了写起来简便,还能顺便禁用 const ::toJSON = { toJSON () { return JSON.stringify(this); } }.toJSON;
const ::json = {
get: { json () { return JSON.stringify(this); } }.json,
}; 然后如果需要赋值的话就是: ::hasOwn = Object.prototype.hasOwnProperty;// 既然不允许覆盖,那也不需要 const 了
get ::size = Map.prototype,__lookupGetter__('size');
const X = function () { return this.valueOf; }
::Y = X;
const Z = ::Y;
const returnThis = ::(){ retun this; };// 无 prototype 的 function,等价于 const returnThis = { '' () { return this; } }[''] |
Beta Was this translation helpful? Give feedback.
-
关于内联语法通常,扩展方法是外置可重用的: function toJSON (replacer, spaces) { return JSON.stringify(this, replacer, spaces); }
const json = value::toJSON(null, '\t'); 但有时候确实可能存在内联书写的需求: const json = value::function (replacer, spaces) { return JSON.stringify(this, replacer, spaces); }(null, '\t'); 不过仔细想来,由于内联扩展天然在上下文中,其实传递其它参数的必要是没有的,甚至函数调用的 const json = value::{ return JSON.stringify(this, null, '\t'); }; 非但简单,甚至避免了创建一个临时函数的开销,使得代码真的内联。这个优势甚至有可能使得扩展语法非但取代 |
Beta Was this translation helpful? Give feedback.
-
关于其它语法糖这些与
|
Beta Was this translation helpful? Give feedback.
-
首先如果看 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 草案,你可以写:
而用 pipe 的话,就麻烦点:
以上。想到再补充。
Beta Was this translation helpful? Give feedback.
All reactions