Understanding Use Cases for Pattern Matching
Tags: python, programming, tutorial, softwaredevelopment
Published: 2023-08-28 01:49:04

Pattern matching has finally been supported in Python since 3.10, and this feature, which is common to many functional programming languages, has been painlessly ported to Python.

However, for those of us who are new to pattern matching, it's hard for us to figure out the right use cases and thus the code we write is not much different from an `if-else`, with at most a little more syntactic sugar in the `if-else` to allow for more fine-grained definitions of the variables.

To understand the real use case of pattern matching, we have to review the paragraph in [PEP 635](https://peps.python.org/pep-0635/#rationale).

> Much of the power of pattern matching comes from the nesting of subpatterns. That the success of a pattern match depends directly on the success of subpattern is thus a cornerstone of the design.

**Nesting of subpatterns** sounds a bit abstract, what are some practical examples of this property?

The easiest data structure to understand is the tree.

Taking a binary tree as an example, traversing from the top to the bottom, each node actually satisfies the following properties.

```python
class TreeNode:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None
```

Each node will have an attribute of its own, as well as a child to the left and right, and this is the typical nested structure. Therefore, one of the most practical examples is to look for tree nodes that fulfill the pattern.

## AST Tree

AST tree is a very common method used for lint or syntax checking.

Suppose we have a requirement to check whether a source code has a direct call to `print`, then we can use `if` to compare the conditions and find out if the node that satisfies the condition violates the lint rule.

```python
for node in ast.walk(tree):
    if (
        isinstance(node, ast.Call)  # It's a call
        and isinstance(node.func, ast.Name)  # It directly invokes a name
        and node.func.id == 'print'  # That name is `print`
    ):
        sys.exit(0)
        
sys.exit(1)
```

From the above code we know we want to match a node `ast.Call` and it is a call function and the function name is print, three conditions in total.

Now, we know the pattern conditions we want to compare, let's rewrite it as follows using pattern matching.

```python
for node in ast.walk(tree):
    match node:
        # If it's a call to `print`
        case ast.Call(func=ast.Name(id='print')):
            sys.exit(0)
            
sys.exit(1)
```

With pattern matching, we can make the conditions more straightforward by comparing them to the `class` definition we have in our head.

From the code we can see pattern matching makes comparing conditions simple, but that's not the power of pattern matching, because we haven't compared nested structures yet. Let's look at the next example.

## Parsing Tree

Suppose now I want to find some Patterns on a binary tree with various colors and shapes.

Given a tree of this kind, I want to know:
- Is there a green square on the tree with a red circle attached to the left node?
- Does the tree have any shape of any color, with a yellow triangle attached to the left node and a blue rectangle attached to the right node?
- The tree has a yellow circle with a blue rectangle attached to the left node. For the blue rectangle, the left node is connected to a red triangle, and the right node is connected to a red rectangle. The red rectangle, the right node is connected to the green square.

Then our thinking model would look like the following diagram.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yndct6teo1d1ar2s7ptr.png)



Actually, that's all our program has to do.

```python
def Find(Tree):
  match Tree:
    case Square('green' , 
            Circle('red') as left , 
            _
          ) as root:
            print(f'case1 \n  {root = } \n {left = } \n ')
      
    case Geometric( _ , 
              Triangle('yellow') as left,
              Rectangle('blue')  as right,
            ):
            print(f'case2 \n {left = } \n {right = } \n')

    case Circle('yellow', 
             _ ,
             Rectangle('blue',
                Triangle('red'),
                Rectangle('red',
                    _ ,
                    Square('green')
                ),
             ) as right
          ) as root:
            print(f'case3 \n {root = } \n {right = } \n ')
            
    case _:
            print('Not Found \n')
```

From this example, we can see the power of pattern matching, not only to compare nodes with explicit conditions, but also to compare fuzzy conditions as in case 2. It's not just easier to write code, it's easier to map the ideas we have in our head directly to the code.

In fact, database and big data engine optimizers make heavy use of pattern matching (not in Python) because the patterns that optimizers need to match are so complex that it would be very difficult to maintain code that simply uses `if-else`.

## Conclusion

Pattern matching is a powerful technique, it's not just a simple `if-else` or `switch-case`, it's a "pattern matching".

However, in our daily lives, we seldom really utilize the power of pattern matching, both because we seldom deal with a large number of nested structures, and because we misunderstand the scenarios applied to them.

Here's a classic example from [Stack Overflow](https://stackoverflow.com/questions/69710333/is-there-a-way-to-match-inequalities-in-python-%E2%89%A5-3-10). I've captured the answer with the most likes.

```python
match a:
    case _ if a < 42:
        print('Less')
    case _ if a == 42:
        print('The answer')
    case _ if a > 42:
        print('Greater')
```

Is this really better than `if-else`?

```python
if a < 42:
    print('Less')
elif a == 42:
    print('The answer')
elif a > 42:
    print('Greater')
```

I believe that most people don't think so, and this is a typical misapplication scenario.

To sum up, the scenario where pattern matching is used is not conditional comparison, but pattern comparison. In addition, the real power of pattern matching is the nested structure.

**Reference**
- [AST Tree](https://stackoverflow.com/questions/71151429/using-python-ast-to-determine-if-a-string-has-been-hard-coded-into-a-print-call/71151862#71151862)
- [Parsing Tree](https://blog.douenergy.com/what-is-the-postgres-sql-game-plan-i/)