Pattern matching trong JavaScript
Có một ECMAScript proposal khá thú vị mới đang ở stage 0 về pattern matching. Tác giả của proposal này là Brian Terlson, tác giả của proposal async/await, và Sebastian Markbåge Tất nhiên vì nó mới ở stage 0 nên bạn vẫn chưa thể thử chạy chỗ code trong bài này được. Nói ngắn gọn về pattern ...
Có một ECMAScript proposal khá thú vị mới đang ở stage 0 về pattern matching. Tác giả của proposal này là Brian Terlson, tác giả của proposal async/await, và Sebastian Markbåge
Tất nhiên vì nó mới ở stage 0 nên bạn vẫn chưa thể thử chạy chỗ code trong bài này được. Nói ngắn gọn về pattern matching thì nó được miêu tả trong proposal thế này
Pattern matching is a way to select behaviors based on the structure of a value in a similar way to destructuring. For example, you can trivially match objects with certain properties and bind the values of those properties in the match leg. Pattern matching enables very terse and highly readable functional patterns and is found in a number of languages. This proposal draws heavy inspiration from Rust and F#.
Examples
Đây là ví dụ ở trong proposal
let getLength = vector => match (vector) { { x, y, z }: Math.sqrt(x ** 2 + y ** 2 + z ** 2), { x, y }: Math.sqrt(x ** 2 + y ** 2), [...]: vector.length, else: { throw new Error("Unknown vector type"); } }
Vì đây là một khái niệm phổ biến trong functional programming, nên hãy thử viết ví dụ trên bằng OCaml
type vector = | Vector2 of float * float | Vector3 of float * float * float | Any of float list let get_length vector = match vector with | Vector3(x, y, z) -> sqrt(x ** 2. +. y ** 2. +. z ** 2.) | Vector2(x, y) -> sqrt(x ** 2. +. y ** 2.) | Any v -> float_of_int(List.length v)
Để dễ giải thích hơn thì hãy bắt đầu bằng một ví dụ đơn giản hơn
const stringOfInt = int => match (int) { 0: 'zero', 1: 'one', 2: 'two', else: 'many' };
Function này trả về các số được viết bằng chữ. Nếu không dùng pattern matching thì thường bạn sẽ viết thế này
// Using switch const stringOfIntSwitch = int => { switch (int) { case 0: return 'zero'; case 1: return 'one' case 2: return 'two' } return 'many' }
Hoặc là nếu không muốn dùng switch thì viết kiểu map thế này
// Using object const stringOfInt = int => ({ 0: 'zero', 1: 'one', 2: 'two' }[int] || 'many');
Tất nhiên như bạn thấy trong ví dụ đầu tiên, các pattern của nó linh hoạt và mạnh hơn rất nhiều. Vậy hãy quay lại ví dụ đầu tiên để xem nó có những pattern gì hay.
More patterns
Bây giờ quay lại ví dụ đầu tiên. Nó là một function nhận một tham số là tọa độ một điểm trong không gian 2 hoặc 3 chiều và trả về độ dài của vector gốc. Hoặc nều tham số là 1 array thì trả về độ lớn của array đó. Còn lại thì là lỗi.
Bạn có thể dễ dàng nhận thấy 2 pattern được sử dụng là Object và Array.
Objects
Hai pattern để match object trong ví dụ đó là { x, y } và { x, y, z }. { x, y } sẽ match một object có hai property x và y, tương tự với { x, y, z }. Như bạn thấy thì nó khá giống với destructuring syntax.
Đây là một ví dụ đơn giản hơn
match ({ x: 3, y: 4 }) { { x, y }: x + y } // 7
Tương tự, bạn cũng có thể match một property với một giá trị nhất định e.g. { x: 3, y }. Ví dụ:
const matchPoint = point => match (point) { { x: 3, y }: y, { x, y: 4 }: x, { x: 3, y: 4 }: null } matchPoint({x: 3, y: 2}) // 2 matchPoint({x: 2, y: 4}) // 2 matchPoint({x: 3, y: 4}) // null
Tất nhiên cả nested property cũng có thể match được
const hasError = response => match (response) { { data: { error } }: true, else: false }
Ngoài ra tuy vẫn chưa được quyết định là có hay không, có thể sẽ có cả pattern để match giá trị của property với điều kiện nhất định như thế này
// points can't have an x or y greater than 100 let isPointOutOfBounds = p => match (p) { { x > 100, y }: true, { x, y > 100 }: true, else: false }
Arrays
Tương tự như destructuring syntax, bạn có thể match array thế này
match (arr) { []: 'empty array', [x]: `array of length 1 with the only element ${x}` }
Tương tự như object syntax, bạn cũng có thể match một giá trị nhất định. Giá trị đó có thể là một primitive value giống như object syntax hoặc thậm chí là một object syntax.
match (arr) { ['a']: 'array with only one element a', ['a', 'b']: 'array with a and b', [{x: 0, y: 0}]: `array with only the 2d origin` }
Rest operator ... có thể cũng có thể dùng tương tự như destructuring syntax
match (arr) { [...]: 'any array', [head, ...]: 'array of length >= 1, bind the head element', [..., tail]: 'array of length >= 1, bind the tail element', [..., t1, t2]: 'array of length >= 2, bind last two elements as t1 and t2', [0, ...]: `array of length >= 1, beginning with 0` }
RegExp
Regular expression cũng có thể được dùng làm match pattern, tuy nhiên không thể dùng trực tiếp
const regexp = /d+/ const isNumber = str => match (str) { regexp: true, else: false }
Tất nhiên proposal mới chỉ ở stage 0, nghĩa là có thể nó sẽ còn thay đổi, hoặc thậm chí còn chẳng được chính thức release trong một phiên bản ECMAScript nào. Tuy nhiên nếu được release thì nó sẽ là một feature rất hữu ích, nhất là với những ai hứng thú với functional programming.