Skip to content

Design Meeting Notes, 8/12/2022 #50325

Description

Pattern Matching

  • [[Overview of current pattern matching proposal.]]

  • Looks roughlylike

    return match (subject) {
      when (0): expr1;
      when (1) if (test1): expr2
      if (test2): expr3
      default: expr4;
    }
    
  • All RHSs are expressions, not statements. If you need statements, you need a do expression which doesn't exist yet.

  • What patterns are there?

    • Combined patterns
      • Pattern and Pattern
      • Pattern or Pattern
    • Interpolation patterns
      • ${ Expression }
    • Identifier patterns:
      • Infinity
      • NaN
      • undefined
      • new bindings otherwise (?)
    • Near-literal patterns: e.g. 0, "some string", null, RegularExpressionLiteral, and TemplateLiteral.
    • Array patterns
    • Object patterns
    • Parenthesized patterns
  • match ([1]) { when ([1, undefined]) ... - does this match?

    • No, incorrect number of elements.
    • Differing behavior with const [x, y] = [1]
  • One can use a value with a custom matcher within an ${interpolation pattern}.

    • Use method named by a proposed Symbol.matcher to return the things that you want to allow a user to bind/destructure after the pattern match.
      • There's a with keyword that allows you to bind against that value.
      • What happens if you destructure properties from the returned object, but they don't exist?
        • We believe the match still succeeds.
          • Differing behavior between pattern matching and with destructuring.
  • Control-flow analysis implications for TypeScript

    • A match should narrow based on the implications of the thing being matched against.

      declare const value: { x: 1 | 2 | "foo" } | { y: false }
        match (value) {
            // 'value' should be narrowed to '{ x: 1 | 2 | "foo" }'
            // Accessing 'value.x' should give a narrowed '1 | 2'
            when ({ x: Number }): value;
        }
      
  • One counter-proposal around "epics"

    • Break down functionality into smaller pieces, make things more teachable.

    • Matching becomes a single primitive that can be brought into several constructs, including maybe a new match expression.

    • let when, etc.

      let { body } when isOk(response);
      
      • Some concerns about this
        • No initializer
        • Single unary function is very specialized.
        • Doesn't work for arrays due to iterable (but this is solvable).
        • "Failure mode" for a match is silent - just falls back to {}.
    • Integration with control flow expressions

      if (let { body } when isOk(response)) {
        // ...
      }
      
      for (let { body } when isOk of responses) {
        // ...
      }
      
      while (let { body } when isOk(responses.pop())) {
        // ...
      }
      
      • Some of these examples echo something very intriguing - but it seems like there's room to integrate on the how of making these consistent and compositional.
    • Then match expressions would invert the difference between matching and binding.

      • Bindings now would need an explicit keyword instead of becoming the default behavior.
      • Matching now becomes the default behavior.
      • We highly prefer explicit binding - though we don't necessarily feel it should be separated from the pattern.
  • Extractor Objects Addition

  • Epics + Extractor Objects

    • is expression

    • Allows you to match and bind.

    • Allows you to create a local from expression position.

      if (container is Box and { containedValue: let value }) {
      // 'value' is now scoped and was set to 'container.value'.
      }
      
    • Like the proposal direction.

      • Cute that you can introduce new bindings - would prefer a let ... in sort of syntax.
      • Not certain about matching on type.
  • Wait, what does ${ and } from the original proposal mean again?

    • It's the difference between matcher vs binding. ${Foo} would say "the thing inside of the curlies (Foo) is a matcher" because otherwise you'd create a variable named Foo
      • "What?"
      • Seems like this prioritization is backwards - matching an existing binding is the common case.

Activity

  1. Thaina commented on Dec 7, 2022

    @Thaina

    I have an idea from the #15480 relate to pattern matching that, in typescript we could extend this feature more than C# to allow pattern matching directly as type constraint in addition to if and switch. That would allow solving #15480 and greatly satisfied similar case of type constraint for every type in almost any use cases

    For example

    function Grade(grading: number when (>= 0) & (<= 100) & (% 10 == 0))
    {
       // grading are constraint to be 0 10 20 30 40 ... 90 100
    }
    
    Grade(10); // fine
    Grade(19); // error
    Grade(120); // error
    
    let value = GetNumberValue(); // number
    if(value >= 0 && value <= 100 && value % 10 == 0)
        Grade(value); // should not error if possible
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design NotesNotes from our design meetings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions