Couldn't we achieve the same functionality with a little less ambiguity using the following syntax?:
not_found = 404
match status:
case not_found:
return "Not found"
case _ as foo:
do_something(foo)
return "Match anything"
it even works for the example in PEP 365 match json_pet:
case {"type": "cat", "name": _ as name, "pattern": _ as pattern}:
return Cat(name, pattern)
case {"type": "dog", "name": _ as name, "breed": _ as breed}:
return Dog(name, breed)
case _:
raise ValueError("Not a suitable pet") match subject
case [first := _, rest := _]
return [rest, first]I would have preferred `as` instead of the walrus operator too.
There are 100s of things in Python who would be confusing to people used to other languages, this is not one of them.
This syntax is clearly an improvemen to the PEP syntax.
And parent said it's "confusing". Well, it's less confusing than the PEP syntax.
Nobody compared it to not adding it at all, or was concerned with improving the language aside from the pattern matching case here.
And I stand by my position that defending a bad decision because of the existence of worse decisions is a really piss poor counterargument.
Disclaimer: I'm a language designer myself so I know first hand how hard it is to get these things right.
This thread was just about the two alternatives (the PEP and explicit capture), not about the PEP in general, or about defending the PEP or even saying that the better alternative is "good". We just say it's better than the PEP. Not sure how you read that into what we wrote.
>Disclaimer: I'm a language designer myself so I know first hand how hard it is to get these things right.
Then go make that argument in some thread in this post discussing the PEP proposal in general?
Bullshit. You said, and I quote, "There are 100s of things in Python who would be confusing". That's what I was responding to. And my point stands: just because there are other pain points in Python doesn't mean we should accept more pain points into it.
> Then go make that argument in some thread in this post discussing the PEP proposal in general?
I’d prefer to make that argument in the thread where I was already talking you about the PEP proposal in general. ;)
It's not a switch! Nowhere does it say switch. It's structural pattern matching!
EDIT: The lack of block scoped variable thing does seem like a wart right enough.
x = -1
for x in range(7):
if x == 6:
print(x, ': for x inside loop')
print(x, ': x in outer')
This outputs: 6 : for x inside loop
6 : x in outer
For consistency, I think it makes sense for match to behave in a similar way.I've been programming in dialects of ML for much longer than Python, so I absolutely appreciate the attraction of this feature. But, the opinion I'm coming to as I think more about this is:
1. I *love* pattern matching.
2. But not in Python.I don't think that's a bad thing. I do think care should be taken when incorporating features from other languages, to see how it interacts with existing features, what the best form of that feature would be, and perhaps whether some different feature could achieve a similar goal.
(For the latter point, I find it unfortunate that languages which already contain 'try/catch' have been introducing 'yield' and 'async/await' as separate features; rather than generalising to 'shift/reset' and implementing all three as libraries)
"I don't think feature X is a good fit for Python because it interacts poorly with existing Python features Y and Z" is not a tacit statement of support for changing features Y and Z. It's a statement that means exactly what it says.
Like I alluded to in my grandparent post, I use other languages that are not Python, and like their features, too. That does not necessarily mean I want all my favorite features from those pulled into Python. Nor do I want my favorite features from Python pulled into other languages.
The best analogy I can think of is a college friend once developed this sort of transitive theory of food that went, "If A tastes good with B, and B tastes good with C, then A must taste good with C." This resulted in a number of questionable concoctions, like serving cottage cheese on top of chocolate cake because they both taste good with strawberries.
Interested in block-level scoping in Python? Please post on the python-ideas mailing list. Thanks.
Current match behavior is consistent with existing syntax and semantics.
And no, not adopting new features known to be generally useful across programming languages is not "better". Instead, better to continue elaborating the language even after the initial pattern matching support is added.
Interested in block-level scoping in Python? Please post on the python-ideas mailing list.
https://news.ycombinator.com/item?id=26082225
We don't shadow variables when they are re-used in comprehensions for example:
>>> i = 4
>>> [i for i in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> i
4
Given they're already accepting the oddity of having "case 404:" and "case variable:" mean very different things, I think they should have just gone the whole way and _not_ mutated outside variables. There seems to be little consistent with this addition to the language.I think "for example" makes this sound more general than it is. List comprehensions are one of the rare few constructs that introduce a new scope. The match block is more syntactically similar to if/while/etc. blocks, and so it makes sense that it would follow similar scoping rules.
This is not to say that I agree with the design as a whole. I think they should have went with:
case obj.some_value:
...
case some_value: # behaves the same way as the previous
...
case _ as variable:
...
case (variable := _): # an alternative to the previous syntax
...
I.e. I think the complete situation is messy, but it's not the variable scoping rules' fault.I would argue that a stack of 'case's are a 'lambda' (and in particular a single 'case x: ...' is equivalent), and hence they should have function scoping like 'lambda' does.
'match' is a distraction; it's just a function call written backwards, i.e. `match x: f` is equivalent to `f(x)` (in this case Python happens to require `f` to be implemented using the stack-of-'case's form, but that seems incidental; similar to how 'raise' and 'except' are a general form of control flow, despite Python requiring values to be wrapped in something descended from 'Exception')
The rarity of the construct doesn't matter honestly. Are you saying that list comprehensions should _not_ have introduced a new scope because it was unusual?
> The match block is more syntactically similar to if/while/etc. blocks, and so it makes sense that it would follow similar scoping rules.
Syntactically yes, but semantically it is very different and the semantics must be taken into account as well. The example given at the top of this thread (case 404: and case variable:) is enough to convince me that having variable scoping is a brain-dead obvious requirement.
> I think the complete situation is messy, but it's not the variable scoping rules' fault.
I agree with that statement. I think that improving the design/semantics would be more effective than just adding some more scoping rules in. In fact, I don't think this PEP should have been accepted in this current form at all. But given the current design, block-level scoping is appropriate. Given another design like maybe those you mention might not require that, but I think focusing on the fact that python doesn't have block-level scoping makes no sense. The python language before this PEP is not the same as the one after this PEP. The new one should have block-level scoping in this instance.
Interested in block-level scoping in Python? Please post on the python-ideas mailing list. Thanks.
Do you have to use the integer literal 404 instead of the named integer `not_found`?
“Use the pin operator ^ when you want to pattern match against a variable’s existing value rather than rebinding the variable.”
iex> not_found = 404
404
iex> ^not_found = 200
** (MatchError) no match of right hand side value: 200
https://elixir-lang.org/getting-started/pattern-matching.htm... match status with
| n when n = not_found -> ...Honestly I think the issue is so troublesome only if there's a single case to match, though. With more expected cases it should cause pretty obvious bugs (easy to catch).
The value being [im]mutable is a different topic entirely.
>>> def f(): pass
>>> g = f
>>> g
<function f at 0x000001B4A686E0D0>
>>> locals()
{'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'__doc__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__name__': '__main__',
'__package__': None,
'__spec__': None,
'f': <function f at 0x000001B4A686E0D0>,
'g': <function f at 0x000001B4A686E0D0>}Because what you're describing fits perfectly into what I would call a variable. There is a mapping from an identifier to a slot that can store a value (or reference), and that mapping is stored in a specific scope. I would call that mapping a variable.
I'm not sure exactly why you mentioned objects that have names embedded in them. Is that relevant to the definition you're using?
With full formality, the definition of "variable" depends on context. In assembly language and C variables are names for storage locations in RAM, in Python they are name bindings in a dict data structure.
One distinction we could make is whether the names are compiled away or kept and used at runtime.
In any event, the important thing is to keep clear in one's mind the semantics of the language one is using. In Python you have values (objects of various types: ints, strings, tuples, lists, functions, classes, instances of classes, types, etc.) some of which are mutable and others are immutable, and you have namespaces: dicts that map strings to values. These namespaces have nothing to do with the location of the values in RAM.
So it doesn't really make sense in Python to speak of "mutate a variable", you can mutate (some) values, and rebind values to names (new or old).
> I'm not sure exactly why you mentioned objects that have names embedded in them. Is that relevant to the definition you're using?
Not really, it's just another little point that sometimes confuses some people when they are coming to grips with the idea that in Python values are not associated with names except by namespace bindings. There are some values (function and class objects) that do get associated with names.
Shouldn't terms like this be cross-language?
> Shouldn't terms like this be cross-language?
Variables in C are different beasties than variables in Python, and variables in Prolog are different from both, and none of those are like variables in math. It's a source of confusion that we use the word "variable" for a host of similar concepts. The word "type" is similarly overloaded in CS. (See https://www.cs.kent.ac.uk/people/staff/srk21/research/papers... )
FWIW, I'm just explaining what wendyshu was on about above. :)
Sure, I elided a bajillion details, and who knows what the CPU is doing under the hood anyway? :)
I've never used rust (yet) so I can't really comment on that.
FWIW namespaces in Python aren't abstract, they exist as dicts at runtime. You can modify the dict you get back from locals() or globals() and change your "variables" in scope:
>>> d = locals()
>>> d['cats'] = 'meow'
>>> cats
'meow'I actually replied to the wrong comment after reading several that visually looked similar at the time, so apologise for causing confusion in this subthread.
This is not specific to "match" statement, but is a general issue in Python. And thus, needs to have a general solution, orthogonal to pattern matching. Are you interested? Please post to the python-ideas mailing list.
I might be misreading the grammar but it looks like you can produce the desired effect by using an attribute, e.g. the following would perform an equality check, not an assignment:
match status:
case requests.codes.not_found:
return "Not found"
The tutorial seems to confirm this: "This will work with any dotted name (like math.pi). However an unqualified name (i.e. a bare name with no dots) will be always interpreted as a capture pattern" match status:
case not_found:
return "Not found"
capture success_code:
return "Returned success code: %s" % success_code
or the "_ as x" or a myriad other ways to make capture explicit... match response:
case (not_found, msg := _):
return f"Not found: {msg}"
case (error, "No swizzle available"):
return "We lack swizzle. :-("
case pair := (_, "Swizzle"):
log(f"Unexpected swizzle: {pair}")
return f"Swizzle?"
case ((code := _), _):
return f"Success: {code}"Now think what will happen if you need to move that not_found variable to the same file as that code (so it will no longer be a dotted name). If you do it manually you need to be extra careful, if you use an automatic tool either it will reject the change or will need to create a dummy class or something in the process.
match status:
case == not_found: # Check for equality
...
match status:
case as not_found: # bind status to variable not_found
...
At least the former should be an option instead of using a dotted name IMO.You know, a lot of potentially confusing behavior would be avoided if programming languages had the sense to make variables read-only by default and disallow variable shadowing altogether.
Compared to having two versions of the same name, one shadowing another?
def neighbourhood(position):
return map(
lambda position: EDGE if position is None else position.absolute,
position.neighbours
)
The inner lambda is shadowing the name 'position'. This does two things:1) It declares that the lambda doesn't depend on the argument of 'neighbourhood'
2) It prevents us referring to that argument by mistake
Compare it to a non-shadowing version:
def neighbourhood(position):
return map(
lambda neighbour: EDGE if neighbour is None else position.absolute,
position.neighbours
)
Oops, I've accidentally written 'position.absolute' instead of 'neighbour.absolute'!This version doesn't make any declaration like (1), so the computer can't help me find or fix the problem; it's a perfectly valid program. A static type checker like mypy wouldn't help me either, since 'position' and 'neighbour' are presumably both the same type.
It's not even clear to a human that there's anything wrong with this code. The problem would only arise during testing (we hope!), and the logic error would have to be manually narrowed-down to this function. Even if we correctly diagnose that the 'if' is returning a different variable than it was checking, the fix is still ambiguous. We could do this:
EDGE if position is None else position.absolute
Or this: EDGE if neighbour is None else neighbour.absolute
Both are consistent, but only the second one matches the shadowing example.I'm going to be honest here, the number of times I've made that kind of mistake is absolutely dwarfed by the number of times I have used the wrong variable because I had accidentally shadowed it.
Neither mistake is super common, but I can't recall ever writing 'position.absolute' instead of 'neighbour.absolute' unless I legitimately needed both position and neighbour in scope and the problem was hard to reason about. I can recall accidentally reusing a variable like 'x' as an iteration variable and then using the wrong 'x' because I forgot, and I can also recall misunderstanding what some piece of code did because I thought 'x' was referring to the outer scope but I had missed that it was shadowed by another declaration. Shadowing has caused me many more problems than it solved, at least in my own experience.
That's a contrived example though, if I ever saw one.
I don't think that's the kind of issue people commonly have, compared to misuse of shadowed variable further down the scope.
And for your example, a better solution would be for the close to declare what it wants to use from its environment. Python doesn't allow this syntax, but some languages do:
def neighbourhood(position): return map( lambda neighbour use (): EDGE if neighbour is None else position.absolute, position.neighbours )
Now the compiler can again warn you, since you're only allowed to use neighbour in the lambda.
In other languages that support match, whether functional or not, you are not changing the value of the variable, but you are shadowing/rebinding the identifier inside the scope of the match clause.
The second example does indeed change the value of `not_found`.
The only clause I can find in the PEP is
> A capture pattern always succeeds. It binds the subject value to the name using the scoping rules for name binding established for named expressions in PEP 572. (Summary: the name becomes a local variable in the closest containing function scope unless there's an applicable nonlocal or global statement.)
This seems... incredibly bad.
(That is, for, if, while, etc. also don't introduce new scopes.)
In Python 2.7 you get this:
>>> my_list = [x for x in 'hello']
>>> x
'o'In my mind that is not well thought out.
Many of the comments here reveal a bizarre parochialism.
let x = foo
in (
case bar of
x -> x,
x
)
This will give `(bar, foo)`: for the first element the `x` in the case will match against `bar` and return it, then that `x` will be discarded as we leave its scope; the second element uses the binding of `x` in the `let`.According to the Python semantics we would get `(bar, bar)`, since we only have one `x` variable. When the case pattern succeeds, the existing `x` is updated to refer to `bar`. Hence we get the `bar` we expect in the first element, but we also get `bar` as the second element, since that `x` variable was updated. (Note that Python guarantees that tuple elements are evaluated from left to right).
I'm honestly _not_ interested in block-level scoping in this case because I would _never_ have wanted this PEP to be accepted. This feature was quite controversial on the various python mailing lists, and yet the steering committee accepted it anyway. The steering committee might consider leading with a bit more humility and _not_ accepting such controversial PEPs. This is an example of language devolution and not evolution.
Scala has always had built-in syntax for pattern-matching, like:
foo match {
case bar => ...
case baz => ...
}
However, Scala also has a thing called `PartialFunction[InputType, OutputType]`, which is a function defined 'case by case' (it's "partial" because we're allowed to leave out some cases). This is essentially a re-usable set of cases, which we can apply to various values just like calling a function.For example we can write:
val f: PartialFunction[A, B] = {
case bar => ...
case baz => ...
}
f(foo)
Scala also allows us to attach extra methods to certain types of value, via 'implicit classes' (which were added late on in Scala's history, although similar patterns were available before). As of Scala 2.13, the standard library attaches a method called `pipe` to values of every type. The `pipe` method simply takes a function and applies it to this/self. For example: val f: PartialFunction[A, B] = {
case bar => ...
case baz => ...
}
foo.pipe(f)
However, now that we have these two things (`PartialFunction` and `pipe`), it turns out we don't need explicit syntax for `match` at all! We can always turn: foo match {
case bar => ...
case baz => ...
}
Into: foo.pipe({
case bar => ...
case baz => ...
})
Hence Scala, in a round-about way, has shown us that pattern-matching is essentially a function call.When it comes to Python, it doesn't even need to be a discussion about block scope; it's equally valid to think of this as function scope (like Python already supports), where `case` acts like `lambda`, except we can define a single function as a combination of multiple `case`s (like in the Scala above).
Yes, there're still remaining 10%, and pattern matching kinda drew attention to those 10%. I'm interested to address those, and invite other interested parties to discuss/work together on that. The meeting place is python-ideas mailing list.
Note that I'm not simply saying 'match should have function scope', I'm saying that 'case' is literally a function definition. Hence functions defined using the 'case' keywork should work the same as functions defined using other keywords ('def', 'lambda' or 'class').
> you have the opposite problem - how to get value from "inner" to "outer" scope
The same way as if we defined the function using 'lambda' or 'def' or 'class'
> it requires "nonlocal" declaration in the inner scope
That's not a general solution, since it doesn't work in 'lambda'; although this exposes the existing problem that there is already a difference between functions defined using 'def'/'class' and functions defined using 'lambda'. Adding yet another way to define functions ('case') which defines functions that act in yet another different way just makes that worse.
And I don't agree with saying it like that. I would agree with "a 'case' could be seen as a function definition". In other words, that's just one possible way to look at it, among others.
Note that from PoV of the functional programming, everything is a function. And block scope is actually recursively lexical lambda.
And OTOH function inlining is a baseline program transformation. Currently in Python, whether a syntactic element (not explicitly a function) gets implemented as a function is an implementation detail. For example, comprehension happen to be implemented as functions. But just as well they could be inlined.
Note that function calls are generally expensive, and even more so in Python. Thus, any optimizing Python implementation would inline whenever it makes sense (called once is obviously such a case). (CPython hardly can be called an optimizing impl, though since 3.8, there's noticeable work on that).
> This feature was quite controversial on the various python mailing lists
I'm also on various Python lists, and what I saw that various details were controversial, not pattern matching itself. Mostly, people wanted pattern matching to be better right from the start, just like many people here. Well, I also want Linux version 234536464576.3.1-final-forever, but instead run 5.4.0 currently, and install new versions from time to time. The same is essentially with Python too.
How does not accepting this PEP throw anything away? It's literally right there. It's still hosted there on the PEP site. Those who want pattern matching can continue to refine the work. "Common sense" requires understanding the current work is a sunk cost and in no way supports its introduction into the language.
> I'm also on various Python lists, and what I saw that various details were controversial, not pattern matching itself.
The details of the PEP are the problem, not the idea. Not accepting this PEP is not the same as rejecting pattern matching. This is only one possible implementation of pattern matching. It's also a bad one and one that makes the language worse. Rejecting this PEP allows a better implementation in the future.
On the PEP site, https://www.python.org/dev/peps/ , there're a lot of deadlocked PEPs, some of them a good and better would have been within, than without.
> Rejecting this PEP allows a better implementation in the future.
Let's count - 3rd-party patmatching libs for Python exists for 10-15 years. And only now some of those people who did their work as "third parties" came to do it inside mainstream Python.
The "future" you talk about is on the order of a decade. (Decade(s) is for example a timespan between 1st attempts to add string interpolation and f-strings landing).
I myself was ardent critic of PEP622/PEP634. I find situation with requiring "case Cls.CONST:" to match against constants to be unacceptable. But I'm pragmatic guy, and had to agree that it can be resolved later. The core pattern matching support added isn't bad at all. Could have been better. Best is the enemy of good.
If it's deadlocked, it really _shouldn't_ be added.
> Let's count - 3rd-party patmatching libs for Python exists for 10-15 years. And only now some of those people who did their work as "third parties" came to do it inside mainstream Python.
What's wrong with multiple implementations? Maybe people want different things? Besides the implementations' existence shows that lack of language support isn't something that blocks the use of pattern matching. Also moving it into the language doesn't mean people will work on that one implementation. Haven't you heard that packages go to the standard library to die? Why would it be any different in the python language. Besides I'm sure that the 3rd party libs will continue to be used anyway.
> But I'm pragmatic guy, and had to agree that it can be resolved later. The core pattern matching support added isn't bad at all. Could have been better. Best is the enemy of good.
I'm pragmatic too. I understand that I can do everything that this PEP introduces without the change to the language. I also understand that this PEP could continue to be worked on and improved. It's true that best is the enemy of good. I (and obviously many others here) believe that this is _bad_.
It's absolutely great, and I'm saying that as someone working 5+ years on an alternative Python dialect (exactly with a motto of "down with toxic lead-acid batteries").
> Also moving it into the language doesn't mean people will work on that one implementation.
Only on that one - god forbid. But gather around that particular implementation to make it better and polish rough edges - for sure. (While the rest of impls will remain niche projects unfortunately.)
> I (and obviously many others here) believe that this is _bad_.
And me, and many others, believe it's good ;).
And that's problem #1 - you'll have hard time to choose among them (even though there're projects with 3.3K stars; but that of course doesn't mean such a project is the "best"). And secondly, many of them are indeed "worse" in the sense they're less general than the PEP version. Third common problem is sucky syntax - unsucky one require macro-like pre-processing of the source, and sadly, that's not a common norm among Python users (it should be, just as the availability of the block scope). I bet you will chicken out on the 3rd point, if not on first 2 ;-).
So yes, "official" support for pattern matching was in the dire need to organize the space. Now, 3rd-party libs can clearly advertise themselves as "We're like official patmatching, but fix the wart X/Y/Z". Bliss.
Well of course I won't use it myself. I don't find it necessary in python. My simple policy will be stand against any usage of this language feature in any code I write or contribute to. Those who want to use cases can either use other language features or third-party libraries which I'd have to study as well. Are you seriously looking down upon me because I haven't used third-party libraries that I consider unnecessary?
> And that's problem #1 - you'll have hard time to choose among them
This point is nonsense. All this shows is there is no agreement on how a third-party package should implement this feature. If anything, it argues against its inclusion in the language.
> And secondly, many of them are indeed "worse" in the sense they're less general than the PEP version.
All this says is that the PEP version isn't the worst implementation out there. It in no way implies that it should be included in the language.
> Third common problem is sucky syntax
So far this is the only time in all your posts in this thread that I've seen you give one reasonable argument. Congrats it took you long enough. So I'll give you this. Make the semantics non-idiotic (i.e. at least fix scoping as well as don't treat variable names and constants differently) and I'll accept it. I'm personally not against pattern-matching. I don't consider necessary by any stretch, but if its design makes sense it is at worst benign.
> So yes, "official" support for pattern matching was in the dire need to organize the space.
It's funny how the vast majority of feedback I see on the internet argues otherwise. It seems pretty clear this was neither needed not implemented well.
Anyway I'll bow out here. You seem less interested in learning what people outside of python-list actually care about or want and more interested in explaining why python-list's position is right. It requires impressive lack of self-reflection. Anyway pattern matching is in. The current form will make python a little worse as a language, but it's still overall quite good language. Maybe improvements will be made to make it tolerable (though I doubt it if your attitude is representative of python-list/python-dev/etc.). If stupidity like this keeps up the language will just slowly devolve, but it's not likely to be a bad language for many many years yet and well there are always other languages to choose from. It's unreasonable to expect a group to make good decisions forever.
Dude, you're just like me! I have the same attitude towards f-strings ;-). Except I know that I will use them sooner or later. But I'm not in hurry. You maybe won't believe, but I found a use even for ":=" operator.
> So far this is the only time in all your posts in this thread that I've seen you give one reasonable argument.
Oh, you're so kind to me!
> You seem less interested in learning what people outside of python-list actually care about or want and more interested in explaining why python-list's position is right.
I'm a flexible guy. On Python lists, I'm argue against f-strings, assignment operators, and about deficiencies in proposed pattern matching. On interwebs with guys like you, I'm arguing trying to help them see the other side. And no worries, your opinion is very important to me.
Well, somewhat tongue-in-cheek, why not introduce a macro system into Python which allows to experimentally implement such syntactic changes as a library?
Secondly, there's a PEP to embrace macros in CPython (instead of pretending they don't exist, and leaving that to external libraries): https://www.python.org/dev/peps/pep-0638/
But the point, you don't need to wait for official PEP to use macros in Python. If you wanted, you could do that yesterday (== decades ago). And I guess in absolute numbers, the same amount of people use macros in Python as in Scheme. It's just in relative figures, it's quite different, given that there're millions of Python users.
(Just in case my answer is: https://github.com/pfalcon/python-imphook , yet another (but this time unsucky, I swear!) module which allows people to implement macros (among other things)).
Just one thing, if mainline Linux would work like Python in respect to stability of APIs and features, you could start to debug and re-write your system after minor kernel upgrades. Linux does not break APIs, and this is possible because people are very careful what they implement - they will need to support it for an indefinite future.
Of course you can make patched branches and experimental releases of the kernel, these exist, but few people will use them, for good reasons.
But the talk was not about that, it was about the fact that we want to get "finished software", but as soon as we ourselves deliver software, we vice-versa want to do it step by step, over long period of time. One day, we should get some reflection and self-awareness and understand that other programmers are exactly like ourselves - can't deliver everything at once.
https://linuxreviews.org/WE_DO_NOT_BREAK_USERSPACE
And here is the rationale why:
https://unix.stackexchange.com/questions/235335/why-is-there...
- you see that it makes perfect sense for a programming language like Python, too, to make only backwards-compatible changes (except perhaps if there are severe problems with a release).
In the same way, it does not matter how things are implemented within Python, but it matters a lot that the user interfaces, which includes in this case the syntax of the language, are stable.
And the fact that Python contrary to that does break backward compatibility - sometimes even in minor releases -, and continues to do so, is a reason that for my own projects I have come to the point at avoiding python for new stuff. There are other languages which are more stable and give the same flexibility, even at better runtime performance.
But who are you, do I know you? I know some guy who said that about Python and now develops his own Python-like language. Is that you? Because if you just consumer of existing languages, it's something different, there always will be a new shiny thingy around the corner to lure you.
> There are other languages which are more stable and give the same flexibility, even at better runtime performance.
Yes, but from bird's eye view, all languages are the same, and differences only emphasize similarities. So, in a contrarian move, I decided to stay with Python, and work on adding missing things to it. Because any language has missing things, and Python isn't bad base to start from at all.
That's exactly what Python does of course (except when purposely otherwise, like 2->3 transition). And of course, that policy is implemented by humans, which are known to err.
What object is the scope of a comprehension (in Py 3; in py 2 they don't have their own scope) a dict attached to? And, if you can answer that why could there not be such an object for a pattern match expression?
The generator object that gets created behind the scenes.
> And, if you can answer that why could there not be such an object for a pattern match expression?
There could be, I suppose, just as there could be for "if" or "for". If Python decided to have lexical scoping everywhere, I would be in favor of that (but then people would complain about breaking changes). In lieu of that, I like the consistency.
So like a match object that could get crreated behind the scenes?
Anyway, anyone agrees that block-level scoping is useful. Interested in block-level scoping in Python? Please post on the python-ideas mailing list. Thanks.
In the general case, you just declare the variable in the surrounding scope and then affect it in the lower one, no?
But yes, block-level scoping (as in C for example) would be useful too in addition to existing whole-function scoping discipline.
Again, I'm looking for similarly-minded people to move this idea forward. If interested, please find me on the python-ideas mailing list for discussing details.
that would be far less likely to break things in an unexpected way, as in "explicit is better than implicit".
I am also wondering whether what is really missing here is perhaps a kind of imperative switch/case statement which has the explicit purpose of changing function variables.
No, its not, but no language (at least that I am aware of) except python does pattern matching + local variables + not introducing a new scope with the pattern match.
Ruby is the closest, but it does introduce a new scope while providing a mechanism for binding variables in the containing local scope. (As well as a method to “pin” variables from the containing scope to use them in matches.)
> (As well as a method to “pin” variables from the containing scope to use them in matches.)
This is a good idea, I agree -- at least for Python, where you would obviously just call __eq__.
EDIT: It looks like you actually can match against constants with this PEP, as long as you access your constant with a dot (e.g., HttpError.NotFound). This seems like a perfectly reasonable solution to me.
Except comprehensions, which changed in Py 3 to have their own scope, rather than binding control variables in the surrounding (function or module) scope as in Py 2.
> It looks like you actually can match against constants with this PEP, as long as you access your constant with a dot (e.g., HttpError.NotFound). This seems like a perfectly reasonable solution to me.
It would be except:
* You can't access function-scoped identifiers that way.
* You can't access module-scoped identifiers in the main module that way.
* You can't conveniently reference identifiers in the current module that way. (I think you can use the full module path to qualify the name in the local module, but that's both awkward and brittle to refactoring, and there's pretty much never a reason to do it for any other purpose.)
This seems like a bizarre misunderstanding of Python scoping rules, and how this can be a problem here.
That breaks one of the most fundamental rules for writing good code.
EDIT: It looks like you actually can match against constants with this PEP, as long as you access your constant with a dot (e.g., HttpError.NotFound). This seems like a perfectly reasonable solution to me.
I guarantee you this will trip up a lot of developers who are either learning the language for the first time or who Python isn’t their primary language.
Worse still, the kind of bugs this will lead to is valid code with unexpected pattern matching, which is a lot harder to debug than invalid code which gets kicked out with a compiler error.
I would NOT expect for the line “case not_found:” to reassign the status variable to 404 regardless of what it was before.
I can’t see how or why that would be intended behavior.
That's what it looks like, but no, the refactoring really does change an equality test into an assignment.
len([]) = 0 len([first|rest]) = 1 + len(rest)
That's a trivial example. The second PEP (pattern matching tutorial) has several other examples:
https://www.python.org/dev/peps/pep-0636/#matching-multiple-...
So, if you use a variable in a pattern, it's an implicit assignment. If you use a literal, it's a value to be matched against.
I agree that the trivial case (a single variable with no conditions) may be confusing before you know what's going on, but I think the alternative, where a single variable is a comparison but multiple variables (or a structure) is an assignment isn't necessarily better.
I would clarify that to implicitly introduce variables.
Consider the following:
$ python3
Python 3.8.6 (default, Dec 28 2020, 20:00:05)
[Clang 7.1.0 (tags/RELEASE_710/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def foo(x):
... return ([x for x in [1, 2, 3]], x, (lambda x: x+1)(x), x)
...
>>> foo(42)
([1, 2, 3], 42, 43, 42)
The list comprehension did not assign the variable x to 1, 2 and 3; it introduced a variable called x, but our original (argument) variable is unaffected (since the second result is 42). Likewise, the nested function doesn't assign its argument variable x, it introduces an argument variable x. Whilst this affects that nested function's result (we get 43), it doesn't affect our original variable, since the final result is still 42.This match syntax seems to be assigning, rather than introducing, which is a shame.
I also think the same as you wrote, the second check will be a hard to spot mistake
To be honest, I feel I like the (sort-of-) "forced namespacing" of matching constants this brings. It should be an easy habit to discipline too, the rule being very simple:
"if you don't want to use literals in your matching, you must plug them in an enumeration"
Too bad that enumeration can't be a PEP 435 enum without adding an ugly `.value` accessor to each case, though.