
On Sun, 2020-06-28 at 19:13 +0300, Ba Ge wrote:
Interesting PEP. Actually it reminds me Crystal language ‘case’. But I have some comments
An excerpt from PEP622 [1]:
Let us start from some anecdotal evidence: isinstance() is one of the most called functions in large scale Python code-bases (by static call count). In particular, when analyzing some multi-million line production code base, it was discovered that isinstance() is the second most called builtin function (after len()). Even taking into account builtin classes, it is still in the top ten. Most of such calls are followed by specific attribute access.
There are two possible conclusions that can be drawn from this information:
* Handling of heterogeneous data (i.e. situations where a variable can take values of multiple types) is common in real world code.
It can be handled with modern Python’ types.
Yes, obviously it is possible to handle with today's language constructs. But that is not the point of the proposal.
* Python doesn't have expressive ways of destructuring object data (i.e. separating the content of an object into multiple variables).
Actually the second reason when isinstance() is used (related to “destructuring”) is the case with inheritance. So the isinstance() switches cases between different successors of some base class. And there would be such a kind of “destructuring”:
if isinstance(something, Window): return something.handle elif isinstance(something, Button): return something.wnd_hnd else: raise SomeError(“blah-blah”)
See, these nested if-statements is what the proposal is aiming to replace.
which is anti-pattern sure: in the case of inheritance, the getters must be delegated to concrete implementations.
I just want to say a big thank you to the Haskell creators for giving us a tool to efficiently write real world code (as it is called above).
The reason why ML family languages have pattern-matching is:
data X = X1 Int Int | X2 String
Python and similar languages don’t support algebraic data types directly (and usually you simulate it with inheritance), so you don’t need pattern-matching and such a way of “destructuring” at all. What is
case x of X1 n m -> …
? It’s a leak of abstraction.
Good point, sounds logical. No ADTs, no need for pattern match. And yet the cited frequency of isinstance() indicates there is a need nevertheless. By the way, any language that has lambda calculus embedded (i.e. anonymous functions as first-class citizens) can emulate algebraic data types and pattern matching via Scott encoding. In that respect, Python does have ADTs. See this recent talk: https://bobkonf.de/2020/thoma.html For example in Haskell you avoid pattern matching on a Maybe via the function maybe :: r -> (a -> r) -> Maybe a -> r because maybe is doing the pattern match for you. In Python the Scott encoding would be something like: class Maybe: def __init__(self,content=None): if content is None: self.nothing() else: self.just(content) def nothing(self): self.maybe = lambda(r,f): r def just(self,a): self.maybe = lambda(r,f): f(a)
A leak of knowledge about INTERNAL structure, internal representation of the object, which is bad. So, if you think that pattern-matching is very good (aka “real world”) , IMHO you are not right here. So C# designers added EXPLICIT support of deconstruct:
private static (string, int, double) QueryCityData(string name) {…} So I need to write this function myself? That is just unnecessary boilerplate from the functional programmer's point of view, isn't it?
so you don’t leak information about internal representation of your data and use just common external general representation like tuples, for example.
Since you argued that Python as it is can do all this already, I'll argue that Haskell can do all this already: One can always choose to not export an ADT's constructors and provide other accessors to pattern match on.
The second helper in C# is a new `switch` syntax, which is very close to pattern-matching, but again: it’s limited with public fields, so only a developer decides which part of his class will be available for matching, and no any leak again. This idea was crystallized in Perl: you don’t know what is the object but do know that it can be used in scalar context/list context/hash context and so on. In OOP, for example, you don’t know what is the object but you know that you can use it as a container of items, which can be enumerable/modifiable-in-place/whatever… This is a right solution from a language designer point of view, but not a ML-style pattern matching. You were just describing Haskell's type classes.
And this understanding is often used in Haskell too – some types are “hidden” for the user: you have only smart constructors/getters but not knowledge about inner structure of the object. IMHO ML-style pattern matching is outdated feature, more typical for 70-80s languages.
Wow, that's a strong statement. We shall see how outdated it is when the adoption of PEP622 is decided upon. It would not be the first time that a mainstream language adopted an old FP construct after many years.
--- Best regards, Paul
I posted this initially because of the techniques used to arrive at the proposal: Sift through large codebases and look for patterns that seem to be workarounds caused by some shortcoming of the language. I don't know the evolution of Haskell very well. Has this ever been done with Haskell? Olaf