Rough exmaple:
switch (x) {
case Number ?n -> console.log(n)
case String ?s -> console.log(s)
case Point {?x, ?y} -> console.log(x, y)
case OtherPattern ?binding -> console.log(binding)
}
注意,绑定前用了?
符号。绑定前面如果能不用问号是最好的,不过可能会有些其他问题,所以暂时先加着——所以看起来可能有点诡异。
Point 是 class,x, y 是fields吗?
是的。(但按JS的术语来说,x、y是property而不是field,也就是也适用于accessor。另外精确的语义,如到底是 has('x' in v),还是 hasOwn,还是 v.x !== undefined,甚或 v.x != null,尚待讨论)
朴素对象就是 Object {}?
可以用 Object
,但实际上应该可以弄成让你直接用 case {?x, ?y} -> ...
就行。
没类型,exhaustive 是靠强制 default 的存在来处理吗?
Plain JS 几乎不可能用类型匹配,或者说基于类型分派没法做成 general 的 pattern match。
exhaustive 不需要强制 default,因为程序员可以自行保证(或由TS之类的类型系统确保)一定有一个匹配,如果最后一个都不匹配,就在运行时扔 TypeError。
不知道支持深层结构的匹配吗?
可以支持的。比如
for (const e of document.all) {
switch (e) {
case {
tagName: 'DL',
children: [
{tagName: 'DT', textContent: ?term},
{tagName: 'DD', textContent: ?desc}
]
} -> console.log(term, desc)
}
}
对象的方法的匹配通过方法名吗?匹配出来丢失了 this binding 还是自动 binding
我的设想是只支持匹配 property(如果是 getter,会用正确的 this),但并不支持调用方法。
选择不支持的原因是,method调用不一定像 getter 那样无副作用(虽然理论上你总是可以写一个有副作用的 getter)。而 pattern match 道理上不应该触发副作用。
但这可以作为 follow-on proposal:
switch (x) {
case Pattern { method(...args): SubPattern } -> ...
}
相当于 _match(x, Pattern) && _match(x.method?.(...args), SubPattern)
如果支持的话,当然应该使用正确的 this。
是否需要 Symbol.patternMatch 去自定义一个对象的模式匹配?
要的。但 built-ins 可以有默认的行为。
const Numeric = {
[Symbol.case](input) {
const result = Number(input)
return {matched: Number.isNaN(result), value: result}
}
}
switch (x) {
case Numeric ?n -> console.log(n)
}
下面代码的语义
switch (input) {
case pattern1 -> code1
case pattern2 ?binding -> code2
}
差不多是
{
const result = _match(pattern1, input)
if (result.matched) { code1 }
else {
const result = _match(pattern2, input)
if (result.matched) {
const binding = result.value
code2
} else {
throw new TypeError('no match!')
}
}
}
function _match(pattern, input) {
if (pattern === undefined || pattern === null) throw new TypeError('pattern can not be nullish')
if (pattern[Symbol.case]) return pattern[Symbol.case](input)
// default
if (!IsObject(pattern)) {
// primitive values like boolean/number/bigint/string/symbol
// (include possible future value types like tuple/record/struct, etc.)
return {matched: pattern === input, value: input}
}
throw new TypeError(`${pattern} is not a valid pattern object`)
}
// Some possible built-ins objects pattern match
// case MyClass ?x -> ...
Object.defineProperty(Function.prototype, Symbol.case, {
value(input) {
return {matched: input instanceof this, value: input}
},
}
// case /regexp/ ?m -> ...
Object.defineProperty(RegExp.prototype, Symbol.case, {
value(input) {
const result = this.global || this.sticky ? this[Symbol.matchAll](input) : this[Symbol.match](input)
return {matched: !!result, value: result}
},
}
// case Array ?a -> ...
Object.defineProperty(Array, Symbol.case, {
value(input) {
return {matched: this.isArray(input), value: input}
}
}
Other cases which may need special treatment for protocol or cross-realm
// case ReferenceError ?err -> ...
// case Iterator ?it -> ...
// case Element ?e -> ...
基于 constructor 的匹配会不会不够细粒度,过于需要基于包含常量的匹配 这样对 tagged unions 可以区分 kind = ‘OK’
感觉上应该可以满足一般的需求,比如
case Result {kind: 'Ok', ?value} -> value
case Result {kind: 'Error', ?reason} -> reason
当然,你可以自行用Symbol.case直接在static构造器上定制一下,这样就可以写
case Result.Ok ?value -> value
case Result.Error ?reason -> reason