Wednesday, October 13, 2021

Inspired Python: Mastering Structural Pattern Matching

Mastering Structural Pattern Matching

If you’re not familiar with the term Structural Pattern Matching then you are not alone. It’s a feature that, until about 10-15 years ago, you would not see outside of functional programming languages. Its use, however, has spread; today you can find a feature like it in C#, Swift, and Ruby. What was once the preserve of niche languages is now available for you to try in Python 3.10.

def greet_person(p):
    """Let's greet a person"""
    match p:
        case {"greeting": greeting, "name": name}:
            print(f"{greeting}, {name}")
        case {"name": name}:
            print(f"Hello, {name}!")
        case {"greeting": _} | {}:
            print("I didn't quite catch your name?")
        case str() as person if person.isupper():
            print("No need to shout - I'm not deaf")
        case str() as person:
            print(f"Nice to meet you, {person}.")
        case _:
            print("I didn't quite understand that!")

Disciples of the functional programming school will surely love it; and seasoned developers who has had to tangle with the umpteenth business rules engine can look forward to some reprieve also. But what about day-to-day use cases? What makes Structural Pattern Matching useful for your typical Python project? What is it even, and why would you want to adopt it when you can solve complex problems without it?

The general concept – and I’ll walk you through how it all works soon enough – goes to the very heart of Computer Science and (especially) functional programming. Permeating all these different languages and their own take on this feature is a common vocabulary and understanding about what Pattern Matching is and the problems it tries to solve. Once you grasp the gist of pattern matching in Python you will recognize – and know how to apply – the concepts anywhere.

Tantalizingly I left a snippet of code heralding the new feature above. It doesn’t look too bad, right? it’s a function that tries to intelligently format a greeting:

>>> greet_person({"greeting": "Say my name"})
I didn't quite catch your name?
>>> greet_person("Walter")
Nice to meet you, Walter.
>>> greet_person({"greeting": "Howdy", "name": "Cosmo"})
Howdy, Cosmo

But there’s nothing in greet_person that you couldn’t do with a series of if statements. And that, right there, is the crux of what pattern matching tries to do: remove the verbiage and tedium of if statements and “getters” that interrogate the structure of an object to extract the information you want. In greet_person I want – ideally – several pieces of information: a greeting and a name, and with graceful handling in case some or all of them is missing.

Manipulating data structures is a core part of programming, and the pattern matching system is there to help you achieve that. When you use if statements, isinstance calls, Exceptions and membership tests against objects, dictionaries, lists, tuples and sets you do so to ensure the structure of the data matches one or more patterns. That is what an ad hoc pattern matching engine looks like.

Consider what the match code above looks like the old-fashioned way:

def greet_person_alt(p):
    msg = "I didn't quite understand that!"
    if isinstance(p, dict):
        if 'greeting' in p:
            greeting = p['greeting']
            if 'name' in p:
                name = p['name']
                msg = f"{greeting}, {name}"
            else:
                # ... etc ...
        else:
            # ... etc ...
    else:
        # ... etc ...
    print(msg)

This is just a part of the whole ordeal, and I made no effort to get clever either. But as you can see, deeply nested if statements make it easy to miss a business rule or put it in the wrong place; even worse, you have to parse the whole structure to figure out the right place to make changes. Not to mention the size of it. Add just a few more rules or complex checks to determine the right greeting format and you would have to create your own home brew matching engine — this approach simply does not scale.

And that, then, brings us to the heart of Structural Pattern Matching: the match and case keywords. This is a problem that you have – and will have – in every facet of programming:

  1. Do you have an inordinately deep and nested dict-of-dicts where you must check for the presence of keys and their values? You could use the structural pattern matcher.

  2. Do you have complex business rules that depend on certain attributes in custom objects, like a Customer or Sales object? You could use the structural pattern matcher.

  3. Do you have to parse the output of files or streams of data from other systems? Maybe transform them from a list of primitives (strings, integers, etc.) into a namedtuple, dictionary or custom dataclass object? You could use the structural pattern matcher.

So let’s take a look at how it really works.



Read More ->

from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...