Understanding Use Cases for Pattern Matching

ChunTing Wu - Aug 28 '23 - - Dev Community

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.

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.

class TreeNode:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None
Enter fullscreen mode Exit fullscreen mode

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.

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)
Enter fullscreen mode Exit fullscreen mode

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.

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)
Enter fullscreen mode Exit fullscreen mode

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

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

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')
Enter fullscreen mode Exit fullscreen mode

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. I've captured the answer with the most likes.

match a:
    case _ if a < 42:
        print('Less')
    case _ if a == 42:
        print('The answer')
    case _ if a > 42:
        print('Greater')
Enter fullscreen mode Exit fullscreen mode

Is this really better than if-else?

if a < 42:
    print('Less')
elif a == 42:
    print('The answer')
elif a > 42:
    print('Greater')
Enter fullscreen mode Exit fullscreen mode

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player