Replies: 17 comments
-
我打算在做完跨语言的 range 语义调查之后切换到 iterable 语义,iterator 语义的确有时候像是 footgun 至于不再能够方便地与 iterator helpers 提案一起使用,我也没有什么很好的想法。 |
Beta Was this translation helpful? Give feedback.
-
还是像 rbuckton 讲的,要寄望于 bind op (或者我准备搞的 extensions 提案)。 用 extensions,就可以直接用现成的库: import *::{take, filter} from 'lodash'
let evens = range(0, Infinity)::filter(x => x % 2 === 0)
let first3evens = evens::take(3) 如果说唯一有点不便,就是需要用什么东西,必须先导入进来(不过也有一点好处:tree-shaking friendly);或者用以下形式 import * as lo from 'lodash'
let evens = range(0, Infinity)::lo:filter(x => x % 2 === 0)
let first3evens = evens::lo:take(3) 我估计,有了这个之后,就没人会再去用那个鸡肋的 iterator helpers 。 😜 |
Beta Was this translation helpful? Give feedback.
-
@hax |
Beta Was this translation helpful? Give feedback.
-
对了,调查过后发现的确多数语言在使用 iterable 语义,但 rust 在用 iterator 语义。而 rust 中也能使用类似 iterator helper 的方法。另外我现在还是决定使用 iterator 语义。 |
Beta Was this translation helpful? Give feedback.
-
@TechQuery 对 |
Beta Was this translation helpful? Give feedback.
-
@Jack-Works 选择使用 iterator 的原因是? |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@Jack-Works 不能简单的说 Rust 使用 iterator 语义,因为 Rust 有 move/borrow 之类的语义和牛逼编译器,所以编译器保证了开发者不会误把 iterator 当成 iterable 。但 JS(和其他语言)没有这个保障。类似的,上次说的D语言之所以是 iterator 是因为它是 struct 具有 copy 语义,所以是集 iterator/iterable 于一身的。 |
Beta Was this translation helpful? Give feedback.
-
关于 rust 可见 tc39/proposal-iterator.range#17 (comment) 自动 copy 的设计我也提过 (tc39/proposal-iterator.range#17 (comment) )但是它在 js api design 里面算比较 crazy 的所以没采用。 |
Beta Was this translation helpful? Give feedback.
-
我还是对此保留。
首先,一个 public api 其实是不知道到底这个东西是不是 generator 的,generator是一个实现手段,用来方便地写 iterator 的。所以我们不应该期望建立 generator function 的心理模型。你最多只能期望建立返回 iterator 的心理模型。 其次,很多普通程序员只知道这里返回一个可以 for ... of 或者 spread 的东西,并不特别理解 iterator 是不可复用的,就算知道,也不见得时时警醒这一点。因为大多数人只是使用api,并对api的结果做出简单预期。那么问题其实在于如何建立大家对 range 不可复用的预期。 对于 API 设计来说,我们优先考虑的「心理模型」是从使用者角度出发的「心理模型」(应该可以这样用)而不是从实现者角度出发的「心理模型」(应该是这样实现的,所以可以或不可以这样用)。但现在这个情况是后一种。 假如我们发现,确实大家有复用的需求,那么怎么办。 在现有的built-in apis里,很少直接返回 iterator,各种keys/values/entries是一个(除了ES6之前的那些返回数组),但是我们要注意到,这些方法都是无参,且其对象本身都是可复用的,也就是你直接复用对象就好了。而range如果返回iterator,就没有复用点了,你要复用的话,要保存所有的参数,或创建一个闭包作为复用点。这增加了误用风险,也增加了复用的难度。 也正是存在复用这个问题,大部分语言里类似的api都是iterable。 所以如果从使用者角度,我认为很难让 特别的,纵观整个JS的 builtins 和 Web platform APIs(但愿我没有遗漏),到目前为止,所有直接返回iterator的方法,也都是无参方法(其实就是一堆 values/keys/entries )。唯一的例外是最近加的 String.prototype.matchAll(regexp) 是一个单参方法。我认为这是一个设计失误,但还好问题不大,因为这个具体case里,对字符串匹配结果的复用需求不是很强。但 range 的复用需求就很明显高于 matchAll,而且 range 是个多参方法,复用难度明显高于 matchAll,结果就是如果你要复用,要么重复多个参数(而且参数如果是计算出来的,还需要重复更多代码和计算,或者引入更多中间变量),要么需要额外的闭包。
iterator helpers 的诱惑实际上严重放大了上述的复用矛盾,helpers 越有用,越可能引发复用的冲动,也越可能在实践上造成bug和引入为复用而需要的额外代码。 注意,一旦误用,我们甚至没有办法去规避bug和做防御性编程,因为iterator不像array之类的可以在参数端和返回处做clone。(注意,你提的「自动 copy」实际上半iterator半iterable,语义更复杂难料,还不如弄成真iterable。) 现在之所以iterator的误用和复用问题不明显,恰恰是因为没有 iterator helpers,iterator上光秃秃的,啥也没有,自然没有人(错误地)复用iterator。而要正确的复用iterator,你最后发现需要的额外代码,还不如放弃 iterator helpers 这个鸡肋,自己搞个 iterable helpers 的库来用。 我认为iterator helpers提案本身这个矛盾就没有解决,在提案里吵了不少,而且现在还是只有 stage 2,甚至如果可能的话,我们应该 block 它。所以最好不要以此为前提来考虑。
如前所述,generator对于public api来说是实现侧的东西,对于使用者是不可见的。
然而所有现有的builtins/platform api都是类class的,所以class对于builtins和platform api来说才真正是「JS style」。
实际上长得像C#并没有什么不好。。。我们有多个feature比如arrow function、async/await、??、?. 等的语法,都是主要来自于C#的。还有很多正在进行的提案也是吸取C#的。(综合来看,JS的好东西早期来自于python,es6之后就主要来自于C#。而且观察历史可见,JS里good parts都是从别的语言抄来的,自己发明的或与其他主流应用编程语言有显著区别的,几乎都是bad parts。iterator helpers就是一个新的例证。。。)
Iterator helpers的DX的问题我前面已经分析了,就是属于乍一看很好,细一想问题很多的「鸡肋」。
可以再看看大家都有什么想法 😂 |
Beta Was this translation helpful? Give feedback.
-
很有道理,的确如此
它不也是从其他语言里借鉴了吗,不是自己造的轮子啊 我接下来还是等下一次的 meeting 上讨论再决定用什么语义吧 |
Beta Was this translation helpful? Give feedback.
-
好吧,我讲 iterator-helpers 可能说过分了。客观地讲,iterator-helpers 不是说不能用,而是需要明确它只是用来构建上层api(或者临时组合来一次性使用)的构件。python itertools 的文档对此讲得比较清楚(a number of iterator building blocks)。 我们可以看到,python的api通常并不直接暴露iterator[1],也就是你总是要从 [1] 简单的搜索后,我只找到一个例外,file object,但它本质上是stream,stream因为必然是一次性消耗的,所以原本就没法复用。管stream叫file object可能是历史因素。 在 iterator-helpers 的 prior arts 里,除了itertools之外, rust、c++的心智模型和体验和js大相径庭,不足为证;lodash 的 api 根本不是针对 iterator 的;linq enumerable根据rbuckton的解释是iterable而不是iterator;ReactiveX/IxJS 明明白白是 iterable helpers 而不是 iterator helpers。 所以我只能说 iterator-helpers 的「借鉴」就是抄走样了。 |
Beta Was this translation helpful? Give feedback.
-
从我的 FP 倾向来说,肯定用相对更无状态的 Iterable 更自然,好歹遍历是幂等的。
@hax 你的意思是有参数就容易被「认为可以存起来」然后被 iterator 的状态坑到是吗?可是那应该怎么设计呢( |
Beta Was this translation helpful? Give feedback.
-
@Huxpro 嗯,很多程序员其实感知不到(意识不到)iterable/iterator区别,尤其如果api本身并不直接暴露iterator时。这个时候有参方法因为多了状态,就更可能会产生复用这个调用结果的需求(无参就不需要,因为可以复用原有对象),所以「认为可以存起来」大概是自然而然的。 关于matchAll,我觉得应该设计成iterable的。当然,matchAll这个例子,在实践上问题可能不大,我主要是认为它开了一个先河——在values/keys/entries无参方法以外的,第一个直接返回iterator的,且是有参的方法——这是没有必要的。 |
Beta Was this translation helpful? Give feedback.
-
@hax 所以当时有关于讨论这个的 issue 不?可能有其他考虑? { [Symbol.iterator] : RegExpStringIterator } 那可能会开一个第一个非集合类 Iterable 的先河? 我能想到的原因比如,Iterator 的概念是允许 lazy eval 得(like Stream),而 Iterable 通常是 pre-allocated 得。 |
Beta Was this translation helpful? Give feedback.
-
这个确实没看到过讨论,但也可能在某个早已关闭的issue。(我刚搜了下repo,也没搜到有这样的讨论)
就是那样啊。从使用者角度来说,绝大多数情况都是 for...of 一下,并没有任何区别。
显然其他语言已经都开过先河了……(假如我们认为range不算集合类的话。)
并不必然,比如 dom live collection,既然是 live 的,就肯定没法 pre-allocated。 |
Beta Was this translation helpful? Give feedback.
-
不过我想了一下,你讲的这个pre-allocate 也是有道理的。matchAll 是一个有一定消耗的运算,如果是可复用的话,不pre-allocate,意味着每次都重复计算。这样想来,返回 iterator 并让程序员自己去保存可复用的结果,也许也是有好处的。(尽管我个人觉得,至少js这样的语言,如果只是为避免可能的重复计算就不提供可复用对象,不是个好选择。。。) |
Beta Was this translation helpful? Give feedback.
-
range 提案当前为了利用 iterator helpers 而设计为直接返回 iterator,虽然看上去挺好用的,但是可能违背程序员预期,因为在大部分(所有?)有类似 range 设施的语言中,都是 iterable。
iterable 和 iterator 的差别是,iterable 通常是可反复迭代的,而 iterator 是一次性消耗的。
一般而言,现有的 API 中 iterator 都是通过调用某个 iterable 对象的方法产生出来的,没有直接返回 iterator 的。因为缺乏 iterable 对象,也就意味着没有可复用的实例。
另一方面,如果改为 iterable,要使用 iterator helpers,就先要转换为 iterator。
尽管理论上 iterator helpers 提案可以进行修订,改为返回可复用的 iterable(只要上游是可复用的 iterable),但从该提案的 champion 的态度看,是不太可能的。
Beta Was this translation helpful? Give feedback.
All reactions