Skip to content

Instantly share code, notes, and snippets.

@raiderrobert
Last active May 3, 2018 12:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raiderrobert/cbacb4c69860ef2eba84020503c01440 to your computer and use it in GitHub Desktop.
Save raiderrobert/cbacb4c69860ef2eba84020503c01440 to your computer and use it in GitHub Desktop.
Pattern Matching Straw Man
# 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

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:

{{PLACEHOLDER FOR EXAMPLES}}

As a continued defense for this specific feature, 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment