<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(it​em)<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>