<div dir="ltr"><div class="gmail_extra">On 8 April 2015 at 05:25, Szymon Pyżalski <span dir="ltr"><<a href="mailto:szymon@pythonista.net" target="_blank">szymon@pythonista.net</a>></span> wrote:<br><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex">
Any object can be a pattern. If an object has a special method (called<br>
``__pattern__`` or ``__match__``) defined then this method will govern<br>
how this object behaves as pattern. If not - pattern matching works<br>
the same as ``__eq__``<br></blockquote><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex">
The ``__pattern__`` method can return ``False`` for no match. For a<br>
match it can return ``True`` (simplest patterns) a sequence or a mapping<br></blockquote><div><br><div class="gmail_default" style="font-family:monospace">Always returning a sequence or None would seem simpler to deal with, wouldn't it?<br><br>I'm imaginging the default implementations would be something like:<br><br></div><div class="gmail_default" style="font-family:monospace"> class object:<br></div><div class="gmail_default" style="font-family:monospace"> def __match__(self, item):<br></div><div class="gmail_default" style="font-family:monospace"> if item == self:<br></div><div class="gmail_default" style="font-family:monospace"> return [item]<br></div><div class="gmail_default" style="font-family:monospace"> else:<br></div><div class="gmail_default" style="font-family:monospace"> return None<br><br></div><div class="gmail_default" style="font-family:monospace"> class type:<br></div><div class="gmail_default" style="font-family:monospace"> def __match__(cls, item):<br></div><div class="gmail_default" style="font-family:monospace"> if isinstance(item, cls):<br></div><div class="gmail_default" style="font-family:monospace"> return [item]<br></div><div class="gmail_default" style="font-family:monospace"> else:<br></div><div class="gmail_default" style="font-family:monospace"> return None<br><br></div><div class="gmail_default" style="font-family:monospace"> class tuple:<br></div><div class="gmail_default" style="font-family:monospace"> def __match__(self, item):<br></div><div class="gmail_default" style="font-family:monospace"> if isinstance(item, tuple):<br></div><div class="gmail_default" style="font-family:monospace"> if all( si.__match__(ii) for si,ii in zip(self, item) ):<br></div><div class="gmail_default" style="font-family:monospace"> return item<br></div><div class="gmail_default" style="font-family:monospace"> <br></div><div class="gmail_default" style="font-family:monospace"> return None<br><br></div></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex">
Syntax for pattern matching<br>
- -------------------------------<br>
The syntax could look something like this::<br>
<br>
for object:<br>
pattern1: statement1<br></blockquote><div><br><div class="gmail_default" style="font-family:monospace">Is the two level indent actually a win? It saves re-evaluating the expression, but you could just as easly have:<br><br></div><div class="gmail_default" style="font-family:monospace"> object = f(x, y+w, g(z/3 for z in x))<br></div><div class="gmail_default" style="font-family:monospace"> match object ~~ pattern1 as a,b,c:<br></div><div class="gmail_default" style="font-family:monospace"> statements...<br></div><div class="gmail_default" style="font-family:monospace"> ormatch object ~~ pattern2 as a,d:<br></div><div class="gmail_default" style="font-family:monospace"> statements...<br></div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">but in that case you're coming pretty close to an 'if' block that's able to set locals:<br><br></div><div class="gmail_default" style="font-family:monospace"> object = ...<br></div><div class="gmail_default" style="font-family:monospace"> if object ~~ pattern1 as a,b,c:<br></div><div class="gmail_default" style="font-family:monospace"> ...<br></div><div class="gmail_default" style="font-family:monospace"> elif object ~~ pattern2 as a,d:<br> ...<br></div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">(Using ~~ as a placeholder for the syntax for "matches against")<br></div><div class="gmail_default" style="font-family:monospace"><br></div></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"> for point:<br>
(Number, Number) as x, y:<br>
print_point(x, y)<br>
{'x': Number, 'y': Number}:<br>
print_point(x, y)<br>
re.compile(<div class="gmail_default" style="font-family:monospace;display:inline"></div>'(?P<x>[0-9]+)-(?P<y>[0-9])+'):<br>
# Or re.compile('([0-9]+)-([0-9])+') as x, y:<br>
print_point(int(x), int(y))<br>
...:<br>
raise TypeError('Expected something else')<br></blockquote><div><br><div class="gmail_default" style="font-family:monospace">My understanding of the way Haskell does name assignment in case matching is that it lets you put placeholders in a constructor. So where you might say<br><br></div><div class="gmail_default" style="font-family:monospace"> x = MyX(a=1, b=2)<br><br></div><div class="gmail_default" style="font-family:monospace">to create an object 'x', you could then say something along the lines of:<br><br></div><div class="gmail_default" style="font-family:monospace"> x ~~ MyX(a=foo, b=bar)<br><br></div><div class="gmail_default" style="font-family:monospace">to pull the values (1, 2) back out (as foo and bar). Doing exactly that doesn't make sense for python because reversing a constructor doesn't make much sense. If you could make that work, you'd get the python version of Haskell pattern matching looking like:<br><br></div><div class="gmail_default" style="font-family:monospace"> re_pt = re.compile('(?P<x>[0-9]+)-(?P<y>[0-9])+')</div><div class="gmail_default" style="font-family:monospace"> case point of:<br></div><div class="gmail_default" style="font-family:monospace"> (Number(x), Number(y)): ...<br></div><div class="gmail_default" style="font-family:monospace"> ('x': Number(x), 'y': Number(y)): ...<br></div><div class="gmail_default" style="font-family:monospace"> re_pt(x=x, y=y): ..<br></div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">where you're at least mentioning the 'x' and 'y' variables that are getting filled in.<br><br></div><div class="gmail_default" style="font-family:monospace">Okay, probably horrible idea:<br><br></div><div class="gmail_default" style="font-family:monospace">Matching syntax looks like:<br></div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> case point is:<br></div><div class="gmail_default" style="font-family:monospace"> (x:=Number, y:=Number): ...<br><br></div><div class="gmail_default" style="font-family:monospace">a:=x is the same as calling assigning_match(locals(), x, "a") run in the local scope. <br>a,b:=x is the same as calling assigning_match(locals(), x, ("a", "b")).<br><br></div><div class="gmail_default" style="font-family:monospace">'case obj is: \n pattern[0]: stmt[0] \n pattern[1]: stmt[1]' does:<br></div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> for i in range(2):<br></div><div class="gmail_default" style="font-family:monospace"> try:<br></div><div class="gmail_default" style="font-family:monospace"> pattern[i].__match__(obj)<br></div><div class="gmail_default" style="font-family:monospace"> except NoMatch:<br></div><div class="gmail_default" style="font-family:monospace"> continue<br></div><div class="gmail_default" style="font-family:monospace"> stmt[i]<br></div><div class="gmail_default" style="font-family:monospace"> break<br></div><br><br><div class="gmail_default" style="font-family:monospace">__match__() raises NoMatch on failure, or returns the match:<br></div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> class object:<br></div><div class="gmail_default" style="font-family:monospace"> def __match__(self, item):<br></div><div class="gmail_default" style="font-family:monospace"> if self == item:<br></div><div class="gmail_default" style="font-family:monospace"> return item<br></div><div class="gmail_default" style="font-family:monospace"> raise NoMatch<br><br></div><div class="gmail_default" style="font-family:monospace"> class type:<br></div><div class="gmail_default" style="font-family:monospace"> def __match__(self, item):<br></div><div class="gmail_default" style="font-family:monospace"> if isinstance(item, self):<br></div><div class="gmail_default" style="font-family:monospace"> return item<br></div><div class="gmail_default" style="font-family:monospace"> raise NoMatch<br></div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> class tuple:<br></div><div class="gmail_default" style="font-family:monospace"> def __match__(self, item):<br></div><div class="gmail_default" style="font-family:monospace"> if isinstance(item, tuple):<br></div><div class="gmail_default" style="font-family:monospace"> if all( s_i.__match__(i_i) for s_i, i_i in zip(self, item) ):<br></div><div class="gmail_default" style="font-family:monospace"> return item<br></div><div class="gmail_default" style="font-family:monospace"> raise NoMatch<br><br></div><div class="gmail_default" style="font-family:monospace"> class dict:<br></div><div class="gmail_default" style="font-family:monospace"> def __match__(self, item):<br></div><div class="gmail_default" style="font-family:monospace"> if isinstance(item, dict):<br></div><div class="gmail_default" style="font-family:monospace"> if item.keys() == self.keys(): <br></div><div class="gmail_default" style="font-family:monospace"> if all(s_v.__match__(item[s_k]) for s_k,s_v in self.items()):<br></div><div class="gmail_default" style="font-family:monospace"> return item<br></div><div class="gmail_default" style="font-family:monospace"> raise NoMatch<br></div><br><div class="gmail_default" style="font-family:monospace"> class SRE_Pattern:<br></div><div class="gmail_default" style="font-family:monospace"> def __match__(self, item):<br></div><div class="gmail_default" style="font-family:monospace"> m = self.match(item)<br></div><div class="gmail_default" style="font-family:monospace"> if m is None:<br></div><div class="gmail_default" style="font-family:monospace"> raise NoMatch<br></div><div class="gmail_default" style="font-family:monospace"> return m.groups()<br></div><br><div class="gmail_default" style="font-family:monospace">assigning_match works something like:</div><br><div class="gmail_default" style="font-family:monospace"> class assigning_match(object):<br></div><div class="gmail_default" style="font-family:monospace"> def __init__(self, l, matchobj, varname):<br></div><div class="gmail_default" style="font-family:monospace"> assert isinstance(varname, str) or isinstance(varname, tuple)<br><br></div><div class="gmail_default" style="font-family:monospace"> self.l = l<br></div><div class="gmail_default" style="font-family:monospace"> self.m = matchobj<br> self.n = varname<br></div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> def __match__(self, item):<br></div><div class="gmail_default" style="font-family:monospace"> r = self.m.__match__(item)<br></div><div class="gmail_default" style="font-family:monospace"> if isinstance(self.n, str):<br></div><div class="gmail_default" style="font-family:monospace"> self.l[self.n] = other<br></div><div class="gmail_default" style="font-family:monospace"> else:<br></div><div class="gmail_default" style="font-family:monospace"> for n, r_i in zip(self.n, r):<br></div><div class="gmail_default" style="font-family:monospace"> self.l[n] = r_i<br></div><div class="gmail_default" style="font-family:monospace"> return r<br></div><br><div class="gmail_default" style="font-family:monospace">Then the example looks like:<br><br></div><div class="gmail_default" style="font-family:monospace"> case point is:<br></div><div class="gmail_default" style="font-family:monospace"> (x:=Number, y:=Number):<br> ...<br></div><div class="gmail_default" style="font-family:monospace"> {"x": x:=Number, "y": y:=Number}:<br> ...<br></div><div class="gmail_default" style="font-family:monospace"> x,y:=re_pt:<br> ...<br><br></div><div class="gmail_default" style="font-family:monospace">Being able to pull out and name something that's a deep part of an object seems like it could be useful; otherwise it's not adding much over if/elif.</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">Though maybe it'd be interesting to have a "deconstructor" protocol for that instead, ie:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> Foo(a,b,key=c) = foo</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">is equivalent to something like:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> x = Foo.__deconstruct__(foo)</div><div class="gmail_default" style="font-family:monospace"> a = x[0]</div><div class="gmail_default" style="font-family:monospace"> b = x[1]</div><div class="gmail_default" style="font-family:monospace"> c = x["key"]</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">I think it might even extend sanely to unpacking subobjects:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> Foo(Bar(a), b) = foo</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">becomes:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> x = Foo.__deconstruct__(foo)</div><div class="gmail_default" style="font-family:monospace"> Bar(a) = x[0]</div><div class="gmail_default" style="font-family:monospace"> b = x[1]</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">becomes:</div><div class="gmail_default" style="font-family:monospace"><div class="gmail_default"><br></div><div class="gmail_default"> x = Foo.__deconstruct__(foo)</div><div class="gmail_default"> x2 = Bar.__deconstruct__(x[0])</div><div class="gmail_default"> a = x2[0]</div><div class="gmail_default"> b = x[1]<br></div><div><br></div></div><div class="gmail_default" style="font-family:monospace">Using "_" to accept and ignore any value would work fine there. If you allowed literals instead of an identifier, you could treat:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> Foo(0, 99, key="x") = foo</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">as:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> x = Foo.__deconstruct__(foo)</div><div class="gmail_default" style="font-family:monospace"> assert 0 == x[0]</div><div class="gmail_default" style="font-family:monospace"> assert 99 == x[1]</div><div class="gmail_default" style="font-family:monospace"> assert "x" = x["key"]</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">That'd be enough to use it for matching I think:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> case foo is:</div><div class="gmail_default" style="font-family:monospace"> Foo(a,b): ...</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">would become:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> try:</div><div class="gmail_default" style="font-family:monospace"> Foo(a,b) = foo</div><div class="gmail_default" style="font-family:monospace"> except:</div><div class="gmail_default" style="font-family:monospace"> continue</div><div class="gmail_default" style="font-family:monospace"> ...<br></div><div class="gmail_default" style="font-family:monospace"> break</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">But it might be confusing figuring out whether:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> b = 0</div><div class="gmail_default" style="font-family:monospace"> case foo is:</div><div class="gmail_default" style="font-family:monospace"> Foo(a,b): print(a)</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">should result in value checking:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> if foo ~~ Foo(a,0): print(a)<br></div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">or value assignment:</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace"> del b</div><div class="gmail_default" style="font-family:monospace"> if foo ~~ Foo(a,b): print(a)</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">Eh, I don't know. Maybe something there is interesting though.</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">Cheers,</div><div class="gmail_default" style="font-family:monospace">aj</div><div class="gmail_default" style="font-family:monospace"><br></div></div></div>-- <br><div>Anthony Towns <<a href="mailto:aj@erisian.com.au" target="_blank">aj@erisian.com.au</a>></div>
</div></div>