On Apr 7, 2015, at 16:35, Grant Jenks <grant.jenks@gmail.com> wrote:

On Tue, Apr 7, 2015 at 3:59 PM, Andrew Barnert <abarnert@yahoo.com.dmarc.invalid> wrote:
The mapping is an interesting end-run around the decomposition problem, but it seems like it throws away the type of the object. In other words, Point(x, y) and Vector(x, y) both pattern-match identically to either {'x': x, 'y': y}. In ML or Haskell, distinguishing between the two is one of the key uses of pattern matching.

And another key use is distinguishing Point(0, y), Point(x, 0) and Point(x, y), which it looks like you also can't do this way; the only way to get x bound in the case block (assuming Point, like most classes, isn't iterable and therefore can't be decomposed as a tuple) is to match a dict.

Wouldn't it be better to be explicit about the type matching?

You mean matching the type of the object? If so, that's my point. A Point and a Vector aren't the same thing just because they have the same two members, which is why in ML or Haskell or my proposal you have to match Point(x, y), not just (x, y) or {'x': x, 'y': y}.

(If you mean the types of the matched elements, the trick is to be able to specify the name and the type in one place; the obvious solution is to extend annotations to assignment/match targets, so you can match Point(x: int, y: int). But anyway, I don't think that's what you meant.)

With a library that doesn't extend Python syntax, that would have to look something like (Point, (q.x, q.y)), which you can probably make a little nicer with some hacks that inspect a compiled function object or a stack frame (to remove the need for quotes around variable names), but then presumably you knew that because you wrote pypatt, which I'm guessing uses one of those hacks. :)

For example, using pypatt, I've done this:

In [19]: import pypatt

In [20]: Point = namedtuple('Point', 'x y')

In [21]: @pypatt.transform
def origin_distance(point):
    with match((type(point), point)):
        with (Point, (0, quote(y))):
            return y
        with (Point, (quote(x), 0)):
            return x
        with (Point, (quote(x), quote(y))):
            return (x ** 2 + y ** 2) ** 0.5
   ....:         

In my proposed syntax, you'd write that as:

    case point:
        of Point(0, y):
            return y
        of Point(x, 0):
            return x
        of Point(x, y):
            return (x ** 2 + y ** 2) ** 0.5

In his syntax, I don't think you can write it at all. You either get a type match or a decomposition, not both, and there's no way (implicitly or explicitly) to match targets to bind and values to check; the best you can do is something like:

    for point:
        Point:
            if x == 0:
                return y
            elif y == 0:
                etc.

... using the fact that Point.__pattern__ returns a dict which is magically dumped into the enclosing scope. So it's simultaneously less explicit and more verbose.

In [22]: origin_distance(Point(0, 5))
Out[22]: 5

In [23]: origin_distance(Point(10, 0))
Out[23]: 10

In [24]: origin_distance(Point(3, 4))
Out[24]: 5.0

In [25]: origin_distance(Point(0, 'far'))
Out[25]: 'far'

In [26]: origin_distance(Point('near', 0))
Out[26]: 'near'


PyPatt looks pretty neat, but I suspect it has a _different_ fundamental problem. As far as I can tell from a quick glance, it doesn't seem to have any way for types to opt in to the matching protocol like Szymon's proposal or mine, which presumably means that it matches by constructing a value and checking it for equality, which means that any type that does anything non-trivial on construction is unmatchable (or maybe even dangerous--consider FileIO(path, 'w') as a pattern). I'm sure Szymon didn't want to add his __pattern__ protocol any more than I wanted to add my __match__ protocol, but once you start trying it with real types I don't think there's any real alternative. (See my linked blog post for more on that.)