Description
openedon Oct 6, 2023
Note
I'd like to continue to update this if it is necessary. This issue currently reflects what we have (in #293).
I'm trying to refactor what ideas we have based on the layering idea. This is also an answer to #305, #281
By layering, I do not mean we must have all features in layer 1 or must defer all features in layer 2.
This layering mostly focused on syntax, not runtime semantics.
I'll analyze this based on whether we can desugar a pattern into the composition of other patterns. (So you'll find the Primitive pattern (1
in { x: 1 }
) is in Layer 2.)
This means we can pick all features from Layer 0 and some features from Layer 1 and 2, then the results become a somewhat usable pattern-matching proposal. (You may want to take a look at https://github.com/codehag/pattern-matching-epic).
I include:
- all "future extensions" (= do not expect it will be in the final proposal, marked by 🤔)
- features without consensus (might be incorrect!) (also marked by 🤔)
Non-pattern structures
Level 0: Basic feature
We should have at least one of them to enter the pattern world.
match
expression (match (expr) { ... }
)- basic clause (
when
, or no keyword at all) (when <pattern>: <expr>,
or<pattern>: <expr>
) - basic clause with guard (
when <pattern> if (<expr>): <expr>,
or<pattern> if (<expr>): <expr>
)- 🍬 desugar: can be removed if we have the
if
pattern
- 🍬 desugar: can be removed if we have the
default
clause (default: <expr>
)if
clause (if (<expr>): <expr>
)- 🍬 desugar: can be removed if we have the
if
pattern
- 🍬 desugar: can be removed if we have the
- basic clause (
is
expression- 🍬 desugar: impossible if the
is
expression enables the possibility to declare a new variable inside an expression ⚠️ concern: block scoped declaration{ if (!(x is [let a, let b])) return; f(a, b) }
- 🍬 desugar: impossible if the
Level 1: Good to have
Maybe future extensions.
if-let
(and alsofor
,while
,do-while
)- based on the
is
expression - or something else like
if match <pattern> (expr) {}
- based on the
- 🤔 try-catch integration
- 🤔 async match
- 🤔 match on function parameter
- 🤔 function overload based on parameter matching
Pattern
Level 0: Basic features, impossible to desugar
- Variable pattern (
expected_value
)🍬 desugar with custom matcher: write a function to match the value
- Object pattern (
{ a: expected_value }
)🍬 desugar with custom matcher: write a function to match the shape and return value as an array⚠️ concern:{ __proto__: f }
means property__proto__
(owned or on the prototype) or[[Prototype]]
or early errors?⚠️ concern: Computed property ({ [Symbol.iterator]: f }
) adds arbitrary expression interpolation⚠️ concern: Shorthand property ({ x }
) means{ x: x }
or{ x: let x }
?⚠️ concern: Rest property without matcher ({ ...x }
) means{ ...x: x }
or{ ...x: let x }
?
- Binding pattern (
{ a: let x }
)
Level 1: Advance features, impossible to desugar
- 🤔 Expression pattern (
${expression}
)⚠️ concern: arbitrary expression interpolation⚠️ concern: interact with custom matcher
- Untagged template literal with interpolation (
{ id: let id, b: `id-${id}` }
)⚠️ concern: arbitrary expression interpolation⚠️ concern: blocks feature that TS have:`a-${let x}-z`
matchesa-99-z
and bindx
to"99"
.
- Iterator pattern (
[1, 2]
)- 🍬 desugar: Impossible for iterators
- 🍬 desugar for Array:
{ 0: let a, 1: let b }
. - 🍬 desugar for Array with custom matcher:
Array(let a, let b)
. ⚠️ concern: ...rest consuming
- Custom matcher pattern (
{ x: Point }
) and extractor pattern ({ x: Point(let x, let y, let z) }
)⚠️ concern: performance of creating match result objects Vague runtime performance concerns about custom matchers #253 and Splitting Variable/Extractor matcher hook? #303⚠️ concern: no way to pass extra parameters / generate a custom matcher on the fly- unless we have expression pattern
{ x: ${SomeFun(param)}(let a, let b) }
- unless we have expression pattern
⚠️ concern: syntax share with variable pattern- cannot know if
{ x: Point }
will call user code or do=== Point
check until runtime - cannot force to do the
=== Point
check- unless we have
if
pattern andand
pattern{ x: let x and if (x === Point) }
- unless we have
===
relational pattern{ x: === Point }
- unless we have
- cannot know if
- 🤔 Auto-generated matcher function for class syntax
⚠️ concern: private field performance problem⚠️ concern: hackable problem⚠️ concern: interact with Symbol.hasInstance
and
pattern (Map and { size: 1 }
)- 🍬 desugar: Possible only for a few use cases, e.g.
{ a: Point and let a }
=>{ a: Point, a: let a }
⚠️ concern: syntax diverge with bool&&
- 🍬 desugar: Possible only for a few use cases, e.g.
or
pattern (1 or 2
)⚠️ concern: interact with binding pattern⚠️ concern: syntax diverge with bool||
not
pattern (not 1
)⚠️ concern: interact with binding pattern⚠️ concern: syntax diverge with bool!
- 🤔
if
pattern (if (expr)
)⚠️ concern: arbitrary expression interpolation⚠️ concern: interact withis
expression
- 🤔 Default values (
{ a = 1 }
)⚠️ concern: arbitrary expression interpolation
Level 2: Ergonomic features
- Simple literal pattern (
null
,true
,1
,2n
,"str"
,NaN
,`abc`
)- 🍬 desugar: extract
let x = ...;
somewhere then match againstx
- 🍬 desugar: extract
- Near literal pattern (
undefined
+1
-1
+1n
-1n
, 🤔+Infinity
, 🤔-Infinity
)⚠️ concern: about+Infinity
and-Infinity
- or more powerful version: Unary algebraic pattern
- 🤔 Unary algebraic pattern (
+0
,-x
)⚠️ concern: what RHS should be? number literal and (dotted) identifier?⚠️ concern: open the door to defining "simple expression" (a rabbit hole) to do matha + 1
- RegEx literal pattern (
/a/
)- 🍬 desugar (with custom matcher): extract
let x = ...;
somewhere then match againstx({ groups: { ... } })
- 🍬 desugar (with custom matcher): extract
- 🤔 RegEx named capture group bindings (
/a(?<let name>)/
)- 🍬 desugar:
/a(?<name>)/({ groups: { let name } })
⚠️ concern: since we cannot add all patterns we have into RegEx syntax, only allow binding looks not so useful
- 🍬 desugar:
- Extended variable pattern (
{ value: Number.POSITIVE_INFINITY }
, 🤔{ x: Array[Symbol.iterator] }
)- 🍬 desugar: extract
let x = ...;
somewhere then match againstx
⚠️ concern: cache[[Get]]
of"POSITIVE_INFINITY"
onNumber
?⚠️ concern: arbitrary expression interpolation (forident[expr]
syntax)
- 🍬 desugar: extract
- Shorthand property binding (
{ let x }
)- 🍬 desugar:
{ x: let x }
- 🍬 desugar:
- 🤔 Shorthand object pattern (
let { x, y }
)- 🍬 desugar:
{ x: let x, y: let y }
⚠️ concern: visual confusion
- 🍬 desugar:
- 🤔 Relation pattern (
>= 2
)- 🍬 desugar with
if
pattern andand
pattern:let x and if (x > 20 && x < 30)
⚠️ concern: what RHS should be? number literal and (dotted) identifier?> 2
,< 2
- 🤔🤔
instanceof x
⚠️ concern: some of us don't like instanceof
- 🤔🤔
=== x
⚠️ concern: looks strange{ constructor: === Point }
- 🤔🤔
"x" in
⚠️ concern: looks strange{ x: "a" in }
- 🤔🤔
typeof "x"
⚠️ concern: we already choose custom matchers to match like{ x: Number }
, not{ x: typeof "number" }
- 🍬 desugar with
- 🤔 Void pattern (
{ a: 1, b: void }
)- 🍬 desugar: create a new unused binding
{ a: 1, b: let _ }
- Note: because we need to also add it into destructing and
using
.
- 🍬 desugar: create a new unused binding
- 🤔 Optional property pattern (
{ a: 1, b?: 2 }
)- 🍬 desugar (with
or
):{ a: 1 } or { a: 1, b: 2 }
- 🍬 desugar (with default value):
{ a: 1, b = undefined }
- 🍬 desugar (with
- 🤔 Binding-while-matching property pattern (
{ let a: Number }
)- 🍬 desugar (with
and
):{ a: Number and let a }
- 🍬 desugar (with
- 🤔 Chaining guards