% Pattern Matching with “With” % python, with, hack, ocaml % 2010-04-28
When I originally thought about adding pattern matching to Python, in the OCaml sense, I ended up using a decorator that more or less registed a bunch of callbacks with a dispatch table based on the types of it’s arguments.
That worked out fine, but it didn’t really have the feel of pattern matching like you get with real algebraic data types. If you recall, I was playing with the following example with the decorator approach:
type astnode =
| AndNode of astnode * astnode
| OrNode of astnode * astnode
| NotNode of astnode
| IdNode of bool
let rec eval_node (n: astnode) =
match n with
| AndNode (l, r) -> (eval_node l) && (eval_node r)
| OrNode (l, r) -> (eval_node l) || (eval_node r)
| NotNode l -> not (eval_node l)
| IdNode v -> v
eval_node (AndNode (IdNode true, IdNode false)) (* returns false *)
The idea of that program was to create a small language to evaluate boolean expressions. In OCaml, it’s quite succinct–too succinct, in all honesty. That’s it. Of course it doesn’t include a parser, or a lexer, but that’s the crux of it.
Since that original post, I’ve posted about two other language hacks that I’ve
attempted to create–both of which use context managers and the
with
-statement, worlds and dispatching urls (a la routes).
Basically, it occurred to me yesterday, that with
’s as
clause did
destructuring of tuples, in the same way that the assignment statement does.
That is to say:
a, b, c = 1, 2, 3
Will correctly assign a = 1
, b = 2
, c = 3
, in the same exact way that:
from contextlib import contextmanager
@contextmanager
def assign(*args):
yield args
with assign(1, 2, 3) as (a, b, c,):
pass
will assign a = 1
, b = 2
, c = 3
.
I’ll admit, that doesn’t look very powerful by itself, but when you consider the possibilities, you might come up with something like I did:
with structural_matching((1, 2, 3)) as match:
with match('list() x y z') as (x, y, z):
print x, y, z
with match('tuple() x _ z') as (x, z):
print "tuple case"
print x, z
which looks incredibly close to pattern matching in OCaml. I was super excited –but it won’t work.
See, match
is a context manager that gets returned with the intention that
if the __enter__()
method raises a NoMatch
exception, it skips the “body”
and goes to the next match. The problem with that thinking however is simple–
there’s no way for __enter__
to force skipping the body due to rejected
PEP-377!
In the example above (full source here), raising NoMatch
in the first
match
block, results in control being passed back to the __exit__()
of the
outer context manager–structural_matching
. And to think, I got my hopes up!
But nevertheless, I pressed on, and hacked together, a match
, that can
destructure the following examples correctly:
with match('[1:3]', [1, 2, 3, 4]) as (a,):
print a
# [2, 3]
with match('[1:]', "hello world") as (a,):
print a
# ('e', 'ello world')
with match('str() x y', 'hello world') as (h, e):
print 'h = ', h, ',',
print 'e = ', e
# h = h, e = e
with match('x y z', [1, 2, 3]) as (x, y, z,):
print z, y, x
# 3 2 1
class obj(object):
def __init__(self, x, y):
self.x = x
self.y = y
with match('obj() .x .y', obj('x-ity', 'y-ity')) as (x, y):
print 'x = ', x, ',',
print 'y = ', y
# x = x-ity, y = y-ity
with match('x y _', [1, 2, 3]) as (x, y):
print x, y
# 1, 2
It’s much less useful considering you can’t put it in the structural_match
block ,like you would in a real match
statement, but it’s all we’ve got.
Back then, I concluded with This is as close to OCaml like pattern matching that we’re going to get, at least as far as I know how to get, but it’s sort of cool, and definitely a hack. Today, I’ll conclude the same way.
ocaml.html