Hi Daniel and Mark,
Sorry for being slightly late to the party, but please let me add a
few remarks of my own to the discussion here.
1. MUST HAVE PRECISELY DEFINED SEMANTICS
Yes, there are some aspects that we left open intentionally. Most
prominently the question of how often the pattern matching engine will
check whether the subject is an instance of a particular class. Take
the following trivial example::
match some_data:
case Pair(12, 34):
...
case Triple(12, 34, z):
...
case Pair(12, y):
...
case Pair(x, y):
...
In a perfect world, the compiler discovers that it must check whether
``some_data`` is an instance of ``Pair`` exactly once and not three
times. This, of course, plays right into Mark's second point on
efficiency and seems obvious enough. Yet, as soon as we are
considering nested patterns, it turns much less obvious whether the
compiler is supposed to cache repeated isinstance-checks. Can we
really expect that the compiler must discover that in both case
clauses the first element is checked against the same class? Or would
it make more sense to simply expect the compiler to potentially
perform this ``Num`` instance check twice::
match some_data:
case [ Num(), 12 ]:
...
case [ Num(), y, *z ]:
...
It is easy to think of cases where we accidentally end up calling an
isinstance check more than once because the compiler could not prove
that they are equal. Still, whenever possible we want to give the
compiler the freedom to optimise the pattern matching statement by
caching.
In a static language, all of this would not be an issue at all, of
course. In Python, however, we end up being caught between its
dynamic features and the desire to make pattern matching reasonably
efficient. So, we ended up leaving the question open as how often the
pattern matching engine is allowed or supposed to check instances.
Naturally, if you go and write some isinstance-check on a class with
side-effects, you can break it.
2. USERS SHOULD NOT HAVE TO PAY AN UNNECESSARY PERFORMANCE PENALTY TO
USE PATTERN MATCHING
To quote Mark [1] here:
/> Users should not have to pay an unnecessary performance penalty to
use pattern matching./
Alright, what does this even mean? What is an unnecessary performance
penalty? How should that be measured or compared?
Pattern matching is not just fancy syntax for an if-elif-statement,
but a new way of writing and expressing structure. There is currently
nothing in Python that fully compares to pattern matching (which is
obviously why we propose to add in the first place). So, do you want
to compare a pattern matching structure to an if-elif-chain or rather
an implementation using reflection and/or the visitor pattern? When
implementing pattern matching, would we be allowed to trade off a
little speed handling the first pattern for moving faster to patterns
further down?
Do our PEPs really read to you like we went out of our ways to make it
slow or inefficient? Sure, we said let's start with an implementation
that is correct and worry about optimising it later. But I thought
this is 101 of software engineering, anyway, and am thus rather
surprised to find this item on the list.
3. FAILED MATCHES SHOULD NOT POLLUTE THE ENCLOSING NAMESPACE
This is a slightly wider issue that has obviously sparked an entire
discussion on this mailing list on scopes.
If there is a good solution that only assigns variables once the
entire pattern matched, I would be very happy with that. However, I
think that variables should be assigned in full before evaluating any
guards---even at the risk of the guard failing and variables being
assigned that are not used later on. Anything else would obviously
introduce a mini-scope and lead to shadowing, which hardly improves
anything with respect to legibility.
4. OBJECTS SHOULD BE ABLE DETERMINE WHICH PATTERNS THEY MATCH
Short version: no!
Class patterns are an extension of instance checks. Leaving out the
meta-classes at this point, it is basically the class that is
responsible for determining if an object is an instance of it.
Pattern matching follows the same logic, whereas Mark suggests to put
that upside-down. Since you certainly do not want to define the
machinery in each instance, you end up delegating the entire thing to
the class, anyway.
I find this suggestion also somewhat strange in light of the history
of our PEPs. We started with a more complex protocol that would allow
for customised patterns, which was then ditched because it was felt as
being too complicated. There is still a possibility to add it later
on, of course. But here we are with Mark proposing to introduce a
complex protocol again. It would obviously also mean that we could
not rely as much on Python's existing infrastructure, which makes
efficient pattern matching harder, again. I completely fail to see
what should be gained by this.
5. IT SHOULD DISTINGUISH BETWEEN A FAILED MATCH AND AN ERRONEOUS PATTERN
This seems like a reasonable idea. However, I do not think it is
compatible to Python's existing culture. Let's pick up Mark's example
of an object ``RemoteCount`` with two attributes ``success`` and
``total``. You can then execute the following line in Python without
getting any issues from the interpreter::
my_remote_count.count = 3
Python does not discover that the attribute should have been ``total``
rather than ``count`` here. From a software engineering perspective,
this is unfortunate and I would be surprised if there was anyone on
this list who was never bitten by this. But this is one of the prices
we have to pay for Python's elegance in other aspects. Requiring that
pattern matching suddenly solves this is just not realistic.
6. SYNTAX AND SEMANTICS
There are some rather strange elements in this, such as the idea that
the OR-pattern should be avoided. In the matching process, you are
also talking about matching an expression (under point 2), for
instance; you might not really be aware of the issues of allowing
expressions in patterns in the first place.
---
It is most certainly a good idea to start with guiding principles to
then design and build a new feature like pattern matching.
Incidentally, this is what we actually did before going into the
details of our proposal. As evidenced by extensive (!) documentation
on our part, there is also a vision behind our proposal for pattern
matching.
In my view, Mark's proposal completely fails to provide a vision or
any rationale for the guiding principles, other than reference to some
mysterious "user" who "should" or "should not" do certain things.
Furthermore, there are various obvious holes and imprecisions that
would have to be addressed.
Kind regards,
Tobias
[1]
https://github.com/markshannon/pattern-matching/blob/master/precise_semantics.rst
Quoting Daniel Moisset <dfmois...@gmail.com>:
[sorry for the duplicate, meant to reply-all]
Thank you for this approach, I find it really helpful to put the
conversation in these terms (semantics and guiding principles).
This is not an answer to the proposal (which I've read and
helps me contextualize) but to your points below and how they apply
to PEP-634. I'm also answering personally, with a reasonable guess
about what the other authors of 634-636 would agree, but they may
correct me if I'm wrong.
On Mon, 16 Nov 2020 at 14:44, Mark Shannon <m...@hotpy.org> wrote:
(...)
I believe that a pattern matching implementation must have the following
properties:
* The semantics must be precisely defined.
* It must be implemented efficiently.
* Failed matches must not pollute the enclosing namespace.
* Objects should be able determine which patterns they match.
* It should be able to handle erroneous patterns, beyond just syntax errors.
PEP 634 and PEP 642 don't have *any* of these properties.
Let me answer this one by one:
1. "The semantics must be precisely defined":
If this happens in PEP634 I don't think it was intentional, and
I'm pretty sure the authors would be happy to complete any
incompleteness that it has. I would happily have a more accurate
description (I drafted a non-official one for a much earlier version
of
PEP-622, https://github.com/dmoisset/notebook/blob/master/python/pep622/semantic-specs.md ). Can you clarify where you see these
imprecisions?
2. "It must be implemented efficiently":
I don't think "efficient implementation" was a priority in
PEP634, although I saw your proposal defines this as "same
performance as the equivalent if statement", and I'm quite sure that
level of performance can be achieved (if it isn't already by
Brandt's implementation). Finding the best way to optimise wasn't a
priority, but I think if there was anything in our implementation
that would make optimisations harder we would consider them as a
change. Do you think anything like that has been presented?
3. "Failed matches must not pollute the enclosing namespace":
This for me is one of the less-desirable parts of the proposal,
and was agreed more as a matter of practicality and an engineering
tradeoff. If you have a reasonable way of solving this (like putting
matched variables in the stack and popping it later) reasonably I'd
be much happier putting that in.
4. "Objects should be able determine which patterns they match."
This is something that you and I, and most of the authors of
622 agree on. What we found out when discussing this is that we
didn't have clear what and how to open that customization. Some
customization options added a lot of complexity at the cost of
performance, some others were very simple but it wasn't clear that
they would be actually useful, or extensible in the future. This has
a lot to do with this being a somewhat new paradigm in Python, and
our lack of knowledge on what the user community may do with it
beyond what we imagined. So the decision was "pattern matching as it
is presented without extensibility is useful, let's get this in, and
once we see how it is used in the wild we'll understand better what
kind of extensibility is valuable". For a crude analogy, imagine
trying to get the descriptor protocol right when the basic python
object model was being implemented. These things happened as
different times as the use of the language evolved, and my opinion
is that customization of the match protocol must follow a similar
path.
5. "It should be able to handle erroneous patterns, beyond just
syntax errors."
I'll be answering this based on the example in your document,
matching RemoteCount(success=True, count=count) where RemoteCount is
a namedtuple. The code is likely an error, and I'm in general all
for reporting errors early, but the kind of error detection proposed
here is the kind of errors that python normally ignore. I find that
example really similar to the kind of error you could make writing
"if remcount.count == 3: ..." or going beyond this example "maxelems
= configfile.read(); if len(elems) == maxelems: ...". There are many
type errors that python ignores (especially related to equality),
and Python has already made the decision of allowing mixed type
equality comparisons everywhere, so why do you think pattern
matching should be different with respect to this? In my opinion
trying to "fix" this (or even agreeing if this is a bug or not) is a
much more general issue unrelated to pattern matching. Given the
current status-quo I normally trust python type checkers to help me
with these errors, and I'd expect them to do the same with the
"erroneous" match statement.
If there are other examples you had in mind when you wrote this I'd
also be happy to discuss those.
I'll try to get some time to review your specific
counterproposal later, thanks for it.
Best,
Daniel
_______________________________________________
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/SIK62L2A3X37G52OR34HGHK6BIDLKGAT/
Code of Conduct: http://python.org/psf/codeofconduct/