[Python-ideas] Pattern Matching Syntax

Robert Roskam raiderrobert at gmail.com
Thu May 3 08:41:47 EDT 2018


Hi Everyone,

Never posted in here before, so I hope that I'm not violating any
particular procedure for intros or something.

Over time, there have been various switch or match statement proposal; some
that have gotten as far as PEPs:

2001 Nov - https://www.python.org/dev/peps/pep-0275/

2006 Jun - https://www.python.org/dev/peps/pep-3103/

2014 Apr -
https://groups.google.com/d/msg/python-ideas/J5O562NKQMY/DrMHwncrmIIJ

2016 May -
https://groups.google.com/d/msg/python-ideas/aninkpPpEAw/wCQ1IH5mAQAJ

However, I don't see that the conversation ever really resolved, so I'd
like restart the conversation on some kind of pattern matching syntax in
Python.

The main objections I've seen are in the following buckets:

   - One--and Preferably Only One--Obvious Way. Basically, we have if/elif
   and that's all we need, so this is syntactical sugar bloat. I'd submit that
   there are specific cases where this kind of syntax would be the obviously
   correct way to do something
   - Specific Syntax Objections. There have been several specific
   objections that usually come down to "unreadable" or "ugly", which are
   subjective statements that don't really bring any good way to continue the
   discussion in a productive manner.

I cannot handle all syntax objections ahead of time, but I can handle the
"only way" objection. At high level, pattern matching provides similar
syntactical sugar to list comprehensions. We could argue that they are
unnecessary since we have for loops. But more importantly, pattern matching
is powerful for what it restricts you to. More specifically:

   - Assignment. Many of the implementations offer the ability to
   immediately assign the value from the matching pattern. However, assignment
   is prevented in the middle of all of the patterns, which is possible in
   if/elif.
   - No Fall Through. Once a pattern is matched, there's no way to break to
   try another branch. Prevents having to look at multiple cases to figure out
   how something resolved. If/elif can have this happen, of course, but even
   more confusing sometimes breaks will be mixed with returns or other control
   flows, which makes figuring how large if/elifs are resolved.
   - Automatic Unpacking. Some implementations offer the ability unpack a
   dictionary equivalent automatically into keys or select ranges of values
   like slicing. Compared to if/elif, this is tremendously more DRY than doing
   the "does the key exists?" and then "what is that keys value?"
   - Guards. Often times you can embed another check to go along with the
   simple pattern matching. Absolutely possible with if/elif, but crucially
   are implementations generally happen after the pattern check. Again, keeps
   code DRY and improves readability.

I figured maybe a good way to continue the discussion is to offer a
straw-man example syntax:

# Simple pattern matching
x = 1

number = match x:
    1 => "one"
    2 => "two"
    3 => "three"
    10 => "ten"
    _ => "anything"

print(number)  # one


# Final Pattern that matches anything
x = 3

number = match x:
    1 => "one"
    2 => "two"
    _ => "anything"

print(number) # anything


# Pattern matching without any match returns None
number = match x:
    1 => "one"
    2 => "two"

print(number)  # None


# Pattern matching with guards
x = 'three'

number = match x:
    1 => "one"
    y if y is str => f'The string is {y}'
    _ => "anything"

print(number)  # The string is three


# Pattern matching with multiple values
x = 1

number = match x:
    1, 2, 3, 4 => "one to four"
    _ => "anything"

print(number)  # one to four


# Pattern matching with types
x = 1.

number = match x:
    x:int => f'{x} is a int'
    x:float => f'{x} is a float'
    x:str => f'{x} is a string'

print(number)  # x is a float


# Supports destructuring dicts

x = {'foo': 1}

number = match x:
    {'foo': 1} => "foo is 1"
    _ => "anything"

print(number)  # foo is 1


# Supports binding with destructuring dicts

x = {'foo': 1, 'bar': 2}

number = match x:
    {'foo': y} => f'got foo {y}'
    {'bar': z} => f'got bar {z}'
    {'foo': y, 'bar': z} => f'got foo {y} and bar {z}'
    _ => "anything"

print(number)  # got foo 1 and bar 2


# Supports destructuring other types too

class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y

point = Point(1,2)

number = match point:
    Point(x,y) => f'point has an x of {x} and y of {y}'
    _ => "anything"

print(number)  # point has an x of 1 and y of 2


As a continued defense for this specific syntax choixe, lets see how two
other languages with this feature handle it. I'm going to try to offer as
nearly as possible similar examples.

Scala https://docs.scala-lang.org/tour/pattern-matching.html

val x: Int = 1

def makeMatch(x:  Any) = x match {
 case 1 => "one"
 case 2 => "two"
 case _ => "anything"
}

val number = makeMatch(x)

Rust https://doc.rust-lang.org/1.5.0/book/match.html

let x = 1;

let number = match x {
    1 => "one",
    2 => "two",
    _ => "anything",
}

And for the sake of completeness, here are other languages with similar
syntax features and their associated documentation

F#
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching

Elixir https://elixir-lang.org/getting-started/case-cond-and-if.html

Clojure https://github.com/clojure/core.match/wiki/Basic-usage

JavaScript (ES2018?) https://github.com/tc39/proposal-pattern-matching

Haskell https://en.wikibooks.org/wiki/Haskell/Pattern_matching

Swift
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180503/cdd3b582/attachment-0001.html>


More information about the Python-ideas mailing list