[Python-Dev] Re: Advantages of pattern matching - a simple comparative analysis

2020-11-24 Thread Brian Coleman
David Mertz wrote:
> On Mon, Nov 23, 2020 at 9:02 PM Brian Coleman brianfcole...@gmail.com
> wrote:
> > Basically, I
> > agree matching/destructuring is a powerful idea.  But I also
> > wonder how much genuinely better it is than a library that does not
> > require
> > a language change.  For example, I could create a library to allow this:
> > m = Matcher(arbitrary_expression)
> >  if m.case("StringNode(s)"):
> > process_string(m.val.s)
> >  elif m.case("[a, 5, 6, b]"):
> > process_two_free_vars(m.val.a, m.val.b)
> > What you are proposing here is very similar in effect to executing pattern
> > matching statements using eval. What is the advantage of implementing the
> > pattern matching functionality in a library if the user interface for that
> > functionality has the same pitfalls as eval?
> > I don't understand the similarity with eval that you are
> suggesting.
> My hypothetical library would store the value passed as initializer to
> Matcher, and provide a method .case().  That method would need
> to do
> some moderately complicated parsing of the pattern passed into it, but
> using parsing techniques, not any eval() call.  Btw. I modified my above
> strawman just slightly to what might be a bit better API.
> If there are any free variables in the pattern, they would be provided by
> the Matcher object.  For example, they could be attributes of the property
> m.val.  Or I suppose we could make them attributes of the Matcher object
> itself, e.g. m.a and m.b, but I think name conflicts with e.g.
> m.case.  Anyway, it's a strawman not an API design.
> If the pattern looked kinda like a class constructor (i.e. exactly the same
> as PEP 634 rules), the .case() method would do an isinstance()
> check
> before doing its other stuff.  If the pattern looks like a list, it would
> scan the freevars inside it and match the constant values.  And so on.
> The actual return value from .m.case(...) would be True or False (or at
> least something truthy or falsy).  However, it MIGHT create some new
> attributes (or keys, or something else) on the Matcher object if the
> pattern actually matched.
> I agree the above is slightly less readable than PEP 635 syntax, but it
> seems to have the entire power of the proposal without changing Python
> syntax.

Because syntax errors in the patterns passed to the `case` method are detected 
at runtime, rather than at compile time, it is necessary to ensure that you 
have code coverage of every call to a `case` method to be confident that there 
are no syntax errors in the patterns.

By comparison, if the pattern matching syntax is built into the language, the 
compiler will detect syntax errors in any and all patterns in `case` clauses. I 
think that this is a major advantage as compared to implementing pattern 
matching via a library.
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/Y4YU3QMQC6XYD6PAGFPGXNT6WZKDP7C4/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Advantages of pattern matching - a simple comparative analysis

2020-11-23 Thread Brian Coleman
David Mertz wrote:
> On Mon, Nov 23, 2020 at 9:02 PM Brian Coleman brianfcole...@gmail.com
> wrote:
> > Basically, I
> > agree matching/destructuring is a powerful idea.  But I also
> > wonder how much genuinely better it is than a library that does not
> > require
> > a language change.  For example, I could create a library to allow this:
> > m = Matcher(arbitrary_expression)
> >  if m.case("StringNode(s)"):
> > process_string(m.val.s)
> >  elif m.case("[a, 5, 6, b]"):
> > process_two_free_vars(m.val.a, m.val.b)
> > What you are proposing here is very similar in effect to executing pattern
> > matching statements using eval. What is the advantage of implementing the
> > pattern matching functionality in a library if the user interface for that
> > functionality has the same pitfalls as eval?
> > I don't understand the similarity with eval that you are
> suggesting.
> My hypothetical library would store the value passed as initializer to
> Matcher, and provide a method .case().  That method would need
> to do
> some moderately complicated parsing of the pattern passed into it, but
> using parsing techniques, not any eval() call.  Btw. I modified my above
> strawman just slightly to what might be a bit better API.
> If there are any free variables in the pattern, they would be provided by
> the Matcher object.  For example, they could be attributes of the property
> m.val.  Or I suppose we could make them attributes of the Matcher object
> itself, e.g. m.a and m.b, but I think name conflicts with e.g.
> m.case.  Anyway, it's a strawman not an API design.
> If the pattern looked kinda like a class constructor (i.e. exactly the same
> as PEP 634 rules), the .case() method would do an isinstance()
> check
> before doing its other stuff.  If the pattern looks like a list, it would
> scan the freevars inside it and match the constant values.  And so on.
> The actual return value from .m.case(...) would be True or False (or at
> least something truthy or falsy).  However, it MIGHT create some new
> attributes (or keys, or something else) on the Matcher object if the
> pattern actually matched.
> I agree the above is slightly less readable than PEP 635 syntax, but it
> seems to have the entire power of the proposal without changing Python
> syntax.

To be more precise, the similarity that I see to `eval` is that syntax errors 
in the patterns that are passed to the `case` method cannot be detected at 
compile time and instead will be detected at runtime. Building the syntax into 
the language gives the advantage of producing a syntax error at compile time. 
It also makes it easier for linters and type checkers to validate the pattern 
matching clauses if the syntax is built into the language.
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/JLWRMQW4OX7SXJWMORHMAIWFES6OYRR7/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Advantages of pattern matching - a simple comparative analysis

2020-11-23 Thread Brian Coleman
David Mertz wrote:
> I have a little bit of skepticism about the pattern matching syntax, for
> similar reasons to those Larry expresses, and that Steve Dower mentioned on
> Discourse.
> Basically, I agree matching/destructuring is a powerful idea.  But I also
> wonder how much genuinely better it is than a library that does not require
> a language change.  For example, I could create a library to allow this:
> m = Matcher(arbitrary_expression)
> if m.case("StringNode(s)"):
> process_string(m.val)
> elif m.case("[a, 5, 6, b]"):
> process_two_free_vars(*m.values)
> elif m.case("PairNone(a, b)"):
> a, b = m.values
> process_pair(a, b)
> elif m.case("DictNode"):
> foo = {key, process_node(child_node) for key, child_node in
> m.values.items()}
> I don't disagree that the pattern mini-language looks nice as syntax.  But
> there's nothing about that mini-language that couldn't be put in a library
> (with the caveat that patterns would need to be quoted in some way).

What you are proposing here is very similar in effect to executing pattern 
matching statements using `eval`. What is the advantage of implementing the 
pattern matching functionality in a library if the user interface for that 
functionality has the same pitfalls as `eval`?
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/E4KVS77FVES6KEH3DQRJMQDZO7WAXWLM/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Advantages of pattern matching - a simple comparative analysis

2020-11-23 Thread Brian Coleman
Antoine Pitrou wrote:
> On Mon, 23 Nov 2020 16:15:12 -
> "Brian Coleman" brianfcole...@gmail.com
> wrote:
> > Furthermore, Python has a regular expression module
> > which implements it's own DSL for the purpose of matching string patterns. 
> > Regular
> > expressions, in a similar way to pattern matching,
> >   allow string patterns to be expressed in a concise and declarative manner.
> > Uh, without regular expressions, a lot of functions would be massively
> more complicated and annoying to write.
> However, your example shows that pattern matching barely makes
> common code shorter (admittedly, on this _one_ example, but that's the
> one you chose ;-)).
> While I agree that regular expressions are far less Pythonic than the
> proposed variant of pattern matching, they also have a tremendously
> better cost/benefit ratio, IMHO.
> Regards
> Antoine.

In my opinion, the object oriented solution to this problem is to use the 
visitor pattern. The solution using the visitor pattern is almost twice the 
length of the other solutions. Pattern matching is certainly no worse than the 
solution using a chain of `isinstance` checks in this case. 

However as the patterns to be checked against a candidate object become more 
complex, I believe that the pattern matching solution will retain the same 
level of elegance and obviousness that it possesses in this example, whereas 
the solution involving a chain of comparisons will quickly degrade in terms of 
obviousness.
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/UNESWNNQGXOI4W24H3HMD6UPDQQDUF2X/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Advantages of pattern matching - a simple comparative analysis

2020-11-23 Thread Brian Coleman
Larry Hastings wrote:
> On 11/23/20 8:15 AM, Brian Coleman wrote:
> > def process(root_node: Node):
> >  def process_node(node: Node):
> >  if isinstance(node, StringNode):
> >  return node.value
> >  elif isinstance(node, NumberNode):
> >  return float(node.value)
> >  elif isinstance(node, ListNode):
> >  return [process_node(child_node) for child_node in 
> > node.children]
> >  elif isinstance(node, DictNode):
> >  return {key: process_node(child_node) for key, child_node in 
> > node.children}
> >  else:
> >  raise Exception('Unexpected node')
> > You don't need the "else".  And you can change all your "elif"s into 
> "if"s too.  Now your "isinstance" version is 35 lines. Shorter than the 
> pattern matching version, roughly the same speed, works in current 
> Python, eminently readable to anybody conversant in current Python.  A 
> very reasonable solution to the problem.
> There should be one--and preferably only one--obvious way to do it,
> //arry/

It was not my intention to suggest that the solution implemented in the 
shortest number of lines was superior.

I think that one of the advantages of the pattern matching implementation over 
the sequence of `isinstance` checks is that there is no need to worry about 
whether using `if`, `elif`or `else` is the best approach. There is a single 
elegant and natural way to express the solution with pattern matching.
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/7FCXBKZQ6PU7VAOB76GGFJJXFWEPXL7L/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Advantages of pattern matching - a simple comparative analysis

2020-11-23 Thread Brian Coleman
Take as an example a function designed to process a tree of nodes similar to 
that which might be output by a JSON parser. There are 4 types of node:

- A node representing JSON strings
- A node representing JSON numbers
- A node representing JSON arrays
- A node representing JSON dictionaries

The function transforms a tree of nodes, beginning at the root node, and 
proceeding recursively through each child node in turn. The result is a Python 
object, with the following transformation applied to each node type:

- A JSON string `->` Python `str`
- A JSON number `->` Python `float`
- A JSON array `->` Python `list`
- A JSON dictionary `->` Python `dict`

I have implemented this function using 3 different approaches:

- The visitor pattern
- `isinstance` checks against the node type
- Pattern matching

Here is the implementation using the visitor pattern:

```
from typing import List, Tuple

class NodeVisitor:
def visit_string_node(self, node: StringNode):
pass

def visit_number_node(self, node: NumberNode):
pass

def visit_list_node(self, node: ListNode):
pass

def visit_dict_node(self, node: DictNode):
pass


class Node:
def visit(visitor: NodeVisitor):
raise NotImplementedError()


class StringNode(Node):
value: str

def visit(self, visitor: NodeVisitor):
visitor.visit_string_node(self)


class NumberNode(Node):
value: str

def visit(self, visitor: NodeVisitor):
visitor.visit_number_node(self)


class ListNode(Node):
children: List[Node]

def visit(self, visitor: NodeVisitor):
visitor.visit_list_node(self)


class DictNode(Node):
children: List[Tuple[str, Node]]

def visit(self, visitor: NodeVisitor):
visitor.visit_dict_node(self)


class Processor(NodeVisitor):
def process(root_node: Node):
return root_node.visit(self)

def visit_string_node(self, node: StringNode):
return node.value

def visit_number_node(self, node: NumberNode):
return float(node.value)

def visit_list_node(self, node: ListNode):
return [child_node.visit(self) for child_node in node.children]

def visit_dict_node(self, node: DictNode):
return {key: child_node.visit(self) for key, child_node in 
node.children}


def process(root_node: Node):
processor = Processor()
return processor.process(root_node)
```

Here is the implementation using `isinstance` checks against the node type:

```
from typing import List, Tuple

class Node:
pass


class StringNode(Node):
value: str


class NumberNode(Node):
value: str


class ListNode(Node):
children: List[Node]


class DictNode(Node):
children: List[Tuple[str, Node]]


def process(root_node: Node):
def process_node(node: Node):
if isinstance(node, StringNode):
return node.value
elif isinstance(node, NumberNode):
return float(node.value)
elif isinstance(node, ListNode):
return [process_node(child_node) for child_node in node.children]
elif isinstance(node, DictNode):
return {key: process_node(child_node) for key, child_node in 
node.children}
else:
raise Exception('Unexpected node')

return process_node(root_node)
```

Finally here is the implementation using pattern matching:

```
from typing import List, Tuple

class Node:
pass


class StringNode(Node):
value: str


class NumberNode(Node):
value: str


class ListNode(Node):
children: List[Node]


class DictNode(Node):
children: List[Tuple[str, Node]]


def process(root_node: Node):
def process_node(node: Node):
match node:
case StringNode(value=str_value):
return str_value
case NumberNode(value=number_value):
return float(number_value)
case ListNode(children=child_nodes):
return [process_node(child_node) for child_node in child_nodes]
case DictNode(children=child_nodes):
return {key: process_node(child_node) for key, child_node in 
child_nodes}
case _:
raise Exception('Unexpected node')

return process_node(root_node)
```

Here are the lengths of the different implementations:

- Pattern matching `->` 37 lines
- `isinstance` checks `->` 36 lines
- The visitor pattern `->` 69 lines

The visitor pattern implementation is by far the most verbose solution, 
weighing in at almost twice the length of the alternative implementations due 
to the large amount of boilerplate that is necessary to achieve double 
dispatch. The pattern matching and `isinstance` check implementations are very 
similar in length for this trivial example.

In each implementation, there are 2 operations performed on each node.

- Determine the type of the node
- Destructure the node to extract the desired data

The visitor pattern and `isinstance` check implementations separate these 2 

[Python-Dev] Re: Words rather than sigils in Structural Pattern Matching

2020-11-23 Thread Brian Coleman
Greg Ewing wrote:
> On 23/11/20 7:49 am, Daniel Moisset wrote:
> > Look at the following (non-pattern-matching)
> > snippet:
> > event = datetime.date(x, month=y, day=z)
> > 
> > The only names that are treated as lvalues there are to the left
> of an '='. The rules are a lot simpler.
> One of the Zen lines says "If it's hard to explain, it's probably
> a bad idea." I think the proposed rules for match cases are
> objectively harder to explain than those for other expressions,
> because they're more complicated.

I don't believe that it is correct that `month` in `month=y` and `day` in 
`day=z` in the expression `event = datetime.date(x, month=y, day=z)` are 
lvalues. They are definitely not assignment targets in the same sense that 
`event` is an assignment target. `month` and `day` are used to bind the 
arguments `y` and `z` to the `month` and `day` arguments accepted by the `date` 
constructor. `event` can be accessed and rebound in the scope that invokes 
`datetime.date`. However, `month` and `day` are only bound to `y` and `z` in 
the scope of the body of the `datetime.date` constructor and are not accessible 
in the scope that invokes `datetime.date`. The behaviour is significantly 
different.
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/FDEVMYO3YCFZO3UYTP7D2G3HHKFP7EXQ/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: The semantics of pattern matching for Python

2020-11-22 Thread Brian Coleman
Regarding the difficulty which some people have respecting class patterns and 
dictionary patterns, I would like to draw attention to a similar feature in 
JavaScript, object destructuring. JavaScript does not have pattern matching but 
object destructuring is closely related. Take the example of a function to 
compute the distance between two points.

```
function distance(p1, p2) {
  // This is an object destructuring assignment that extracts the x field of p1 
into x1 and the y field of p1 into y1
  const { x: x1, y: y1 } = p1;
  // This is an object destructuring assignment that extracts the x field of p2 
into x2 and the y field of p2 into y2
  const { x: x2, y: y2 } = p2;

  const dx = x2 - x1;
  const dy = y2 - y1;
  return Math.sqrt(dx*dx + dy*dy);
}

// An object literal is assigned to p1 here
const p1 = { x: 0, y: 0 };
// An object literal is assigned to p2 here
const p2 = { x: 1, y: 1 };
const d = distance(p1, p2);
```

Similarly to dictionary patterns in Python, object destructuring in JavaScript 
places the variable names that are the targets of the assignments in the same 
position where a value would be expected in an object literal. This feature has 
existed in JavaScript for several years now and I would like to draw attention 
to it as a counterpoint to the argument that pattern matching is not a good fit 
for languages that are not statically typed. Destructuring can also be found in 
Common Lisp, which is not statically typed. Pattern matching is also a core 
part of Erlang, which is not statically typed..

I would also like to draw attention to the fact that object destructuring was 
added to JavaScript years into it's development. I do not believe this to be a 
reasonable argument against adopting pattern matching in Python.
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/6XDXHVT3ZFOK66GVU5UWYGSHJX4UF2CW/
Code of Conduct: http://python.org/psf/codeofconduct/