gvanrossum / patma

Pattern Matching
1.02k stars 64 forks source link

Expressions vs. Statements #94

Open viridia opened 4 years ago

viridia commented 4 years ago

I'm only creating an issue on this because (a) it was mentioned a couple of times on python-dev and (b) we should track the discussion here (I couldn't find an existing issue that covers this). I'm not seriously contending that we make match an expression, but I want to discuss the pros and cons. In particular, I don't think the "it's tradition" argument is sufficient by itself, at least without an examination of what tradition means.

If this were any modern language other than Python, I think this would be a different story. Most of the popular languages that have been introduced in the last 20 years (Swift, Rust, TypeScript to name a few) accommodate a hybrid style which is mid-way between the strict 'functional' approach, and the older Algol-style imperative syntax. That is, you can mix the functional and imperative styles freely within a function, choosing whatever is the most suitable.

Thus I can choose between either of these:

const selectedItems = [];
for (let item of items) {
  if (item.selected) {
    selectedItems.push(item);
  }
}
const selectedItems = items.filter(item => item.selected);

These are equivalent semantically, although they have different performance characteristics.

In my own coding I find this hybrid approach very natural. As I have written elsewhere, I tend to use a lot of callbacks and arrow functions in my code, and I tend to think of higher-order functions as a way to introduce new kinds of control-flow statements into the language without actually modifying the language syntax or changing the compiler.

Python, on the other hand, has taken a different approach, which is to introduce new syntax (list comprehensions, mapping comprehensions, and so on) which support the most common "functional" use-cases, thereby reducing the pressure for more general functional-ish features in the language. So I can say:

selectedItems = [item for item in items if item.selected]

Of course, one downside to this is that if the filter condition is complex, requiring more than one statement, then you can't embed it in a comprehension. But the solution to that is simple, which is to put the complex logic in a function. In fact, this is Python's general answer to functional programming, which is to declare a named function and use 'return' to yield the values you want.

This works for match as well - if you want match to yield a value, you can put the match statement inside of a function and have each case clause return a value. In fact, in my example program expr.py I found that one-match-statement-per-function was not a huge burden, since most of the logic was visitor functions, each of which had a single match statement inside of them.

As I see it, the biggest problem with making match an expression is not the specific details of syntax, but the fact that it impacts the rest of Python in a huge way, making the language almost unrecognizable from what it is today. Here's my reasoning:

1) Simply making 'match' an expression is insufficient and useless without also changing the semantics of blocks / suites.

I know that some languages adopt a rule that the last statement in a block is the 'value' of the block. Changing the meaning of blocks seems like a rather drastic change.

Alternatively, you could introduce some new EIBTI syntax to indicate the value of a block:

x = match y:
    case int(value):
         print(value, "is an int")
         => value + 1
    case str(value):
         print(value, "is a str")
         => f"{value} + 1"

Where => means "yield this value as the value of the block". (I chose => because I couldn't think of a good keyword.)

2) Now that you've introduced the idea that blocks can return values, there will be never-ending pressure to make other Python statements expressions as well - why can't if or try blocks also return values?

And the thing is, once you do this, it won't be used just by a small handful of functional programming enthusiasts. In my experience, in languages that support expression-based programming this pattern occurs everywhere. Every Python app or library that is written from that point on is going to be affected.

I know it's strange to reject an idea because it might be too popular, but consider that Python is also supposed to be easy to understand for beginners, and while some beginners with a more mathematical perspective might consider this an easy concept to grasp, others might not.

This "camel's nose in the tent" argument is problematic because now we're no longer confining the discussion to a single PEP about adding a new statement, but fundamentally changing the look of the whole language.

gvanrossum commented 4 years ago

Yes. Basically making this an expression would require making all statements expressions.

jstasiak commented 4 years ago

As a person who raised this in the PEP 622 discussion on python-dev I have to say I'm... convinced, sadly. I've wanted to put my code where my mouth is and come up with a proof of concept but got stuck on the difficulties (re)iterated in this summary and decided it's not worth the effort (at this time).

Tobias-Kohn commented 4 years ago

@viridia This is a very nicely expressed statement (sorry for the pun :wink:) and I could not agree more.

As I am switching constantly between Scala and Python, I have been bitten a couple of times by Python requiring that return in the end. But I would not have it any other way. I have also taught both Scala and Python at high school and can absolutely confirm that my students were struggling with the "everything is an expression" thing.