[Python-ideas] ML Style Pattern Matching for Python

Eike Welk eike.welk at gmx.net
Sat Dec 18 00:21:35 CET 2010


After learning a bit of Ocaml I started to like its pattern matching features. 
Since then I want to have a "match" statement in Python. I wonder if anybody 
else would like this too.

ML style pattern matching is syntactic sugar, that combines "if" statements 
with tuple unpacking, access to object attributes, and assignments. It is a 
compact, yet very readable syntax for algorithms, that would otherwise require 
nested "if" statements. It is especially useful for writing interpreters, and 
processing complex trees.

Instead of a specification in BNF, here is a function written with the 
proposed pattern matching syntax. It demonstrates the features that I find 
most important. The comments and the print statements explain what is done.  


Proposed Syntax
---------------

def foo(x):
    match x with
    | 1 ->                      # Equality
        print("x is equal to 1")
    | a:int ->                  # Type check
        print("x has type int: %s" % a)
    | (a, b) ->                 # Tuple unpacking
        print("x is a tuple with length 2: (%s, %s)" % (a, b))
    | {| a, b |} ->             # Attribute existence and access
        print("x is an object with attributes 'a' and 'b'.")
        print("a=%s, b=%s" % (a, b))

    # Additional condition
    | (a, b, c) with a > b ->
        print("x is a tuple with length 3: (%s, %s, %s)" % (a, b, c))
        print("The first element is greater than the second element.")

    # Complex case
    | {| c:int, d=1 |}:Foo ->
        print("x has type Foo")
        print("x is an object with attributes 'c' and 'd'.")
        print("'c' has type 'int', 'd' is equal to 1.")
        print("c=%s, d=%s" % (c, d))

    # Default case
    | _ ->
        print("x can be anything")


Equivalent Current Python
-------------------------

The first four cases could be handled more simply, but handling all cases in 
the same way leads IMHO to more simple code overall.

def foo(x):
    while True:
        # Equality
        if x == 1:
            print("x is equal to 1")
            break

        # Type check
        if isinstance(x, int):
            a = x
            print("x is an integer: %s" % a)
            break

        # Tuple unpacking
        if isinstance(x, tuple) and len(x) == 2:
            a, b = x
            print("x is a tuple with length 2: (%s, %s)" % (a, b))
            break

        # Attribute existence testing and access
        if hasattr(x, "a") and hasattr(x, "b"):
            a, b = x.a, x.b
            print("x is an object with attributes 'a' and 'b'.")
            print("a=%s, b=%s" % (a, b))
            break

        # Additional condition
        if isinstance(x, tuple) and len(x) == 3:
            a, b, c = x
            if a > b :
                print("x is a tuple with length 3: (%s, %s, %s)" % (a, b, c))
                print("The first element is greater than the second "
                        "element.")
                break

        # Complex case
        if isinstance(x, Foo) and hasattr(x, "c") and hasattr(x, "d"):
            c, d = x.c, x.d
            if isinstance(c, int) and d == 1:
                print("x has type Foo")
                print("x is an object with attributes 'c' and 'd'.")
                print("'c' has type 'int', 'd' is equal to 1.")
                print("c=%s, d=%s" % (c, d))
                break

        # Default case
        print("x can be anything")
        break


Additional Code to Run Function "foo"
-------------------------------------

class Bar(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        
class Foo(object):
    def __init__(self, c, d):
        self.c = c
        self.d = d
        
        
foo(1)          # Equality
foo(2)          # Type check
foo((1, 2))     # Tuple unpacking
foo(Bar(1, 2))  # Attribute existence testing and access
foo((2, 1, 3))  # Additional condition
foo(Foo(2, 1))  # Complex case
foo("hello")    # Default case



I left out dict and set, because I'm not sure how they should be handled. I 
think list should be handled like tuples. Probably there should be a universal 
matching syntax for all sequences, similarly to the already existing syntax:
    a, b, *c = s

I don't really like the "->" digraph at the end of each match case. A colon 
would be much more consistent, but I use colons already for type checking 
(a:int).

I generally think that Python should acquire more features from functional 
languages. In analogy to "RPython" it should ultimately lead to "MLPython", a 
subset of the Python language that can be type checked and reasoned about by 
external tools, similarly to what is possible with Ocaml.



Eike.



More information about the Python-ideas mailing list