Now that we've promoted generics and type aliases to syntax, why not do the same for enums, structs and abstract classes?

I’m not sure which would correspond to structs/product types, dataclasses or TypedDicts (most probably the former), but with enums/sum types and (abstract) classes, Python is gonna be a way more intuitive and powerful language to model data:

Enums

Now

from enum import Enum

class Colour(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

New

enum Colour:
    Red
    Green
    Blue(light: bool)
match colour:
    case Colour.Red:
        print("red")
    case Colour.Green:
        print("green")
    case Colour.Blue(light=false):
        print("dark blue")
    case Colour.Blue(light=true):
        print("light blue")

Structs

Now

from dataclasses import dataclass

@dataclass
class InventoryItem:
    name: str
    unit_price: float
    quantity_on_hand: int = 0

New

struct InventoryItem:
    name: str
    unit_price: float
    quantity_on_hand: int = 0

Abstract classes

Now

from abc import ABC

class Eq[Rhs](ABC):
    def __eq__(self, other: Rhs) -> bool:
        pass

    def __ne__(self, other: Rhs) -> bool:
        return not self = other

New

abstract Eq[Rhs]:
    def __eq__(self, other: Rhs) -> bool:
        pass

    def __ne__(self, other: Rhs) -> bool:
        return not self = other

or just (:p)

class Eq[Rhs]:
    def __eq__(self, other: Rhs) -> bool:
        pass

    def __ne__(self, other: Rhs) -> bool:
        return not self = other

and then

impl Eq for MyType:
    ...
def eq[T: Eq](a: T, b: T) -> bool:
    return a == b

(By the way, can we just terminate declarations by newlines or ;s, in stead of mandatory passs?)

class Eq[Rhs]:
    def __eq__(self, other: Rhs) -> bool

    def __ne__(self, other: Rhs) -> bool:
        return not self = other

or

class Eq[Rhs]:
    def __eq__(self, other: Rhs) -> bool;

    def __ne__(self, other: Rhs) -> bool:
        return not self = other

Macros/metaclasses/decorators

TBD

It’d be worth reconsidering PEP 542 – Dot Notation Assignment In Function Header to deprecate metaclasses.

Thanks.

2 Likes

This is not really typing related right so it should probably go into ideas?

Now that we’ve promoted generics and type aliases to syntax, why not do the same for enums, structs and abstract classes?

Benefits? The before and after examples provided look almost the same.

The barrier for introducing new syntax to Python is generally pretty high.

Different topic. Please open different thread.

Very different topic. Please open different thread.

4 Likes

I’d vote no to having two ways to do the same thing where the new way doesn’t have a substantial user-benefit than the old way.

Also, I’d say no to adding more reserved words that are likely to break existing code. I have a feeling special cases for something like enum, etc. would also further complicate the python grammar which likely would have other downstream cost/maintenance.

Finally: the semicolon example doesn’t really make sense in __eq__ example since it wouldn’t return the given bool annotation, and would instead give None since ; would be equivalent to pass.

3 Likes

In the abstract classes, only function headers/signatures matter, separate from their to-be-implemented bodies. Having a syntax that lets you omit the whole body part at all, where passs and even : are not necessary, IS actually the manifestation of abstract classes itself. So it’s not a different topic but related and important syntactical difference, as you can observe that many Python community people are accustomed to thinking those mere declarations SHOULD return something – even None – which is not the point of abstract classes (but they can have default implementations by the way).

In the system suggested here, dataclasses would be refactored into structs AND some auto-implementation mechanism (the Macros/metaclasses/decorators part) for example:

@derive(Repr, Eq, Order)
struct InventoryItem:
    name: str
    unit_price: float
    quantity_on_hand: int = 0

and to dynamically achieve this, I just thought PEP 542 would be a good option unless Python gets neat static macro/template mechanisms. So anyway, this is not a different topic again.

Thanks.

This feels very much like you want to use Rust syntax in Python, rather than just using Python’s existing approaches…

1 Like

That’s obviously the question I’m asking here to those who can immediately understand what I meant:

What would you think about Python’s class system after some learning period of Rust’s type and trait system? Do we share the same understanding of how Python’s class could have been, and can be refactored today just only if we want?

I’d like to know how especially the core devs understand such language mechanisms of Python. They’ve super-successfully managed to incorporate pattern-matching and generics/type aliases into the language.

Thanks.

I think Rust’s syntax is great for Rust, but I don’t see any particular benefits in trying to retro-fit it to Python for constructs where Python already has a way of doing the same thing.

6 Likes

Python and Rust differ in more than just syntax. Python’s object system has unique capabilities specifically because of metaclasses and dynamic typing that Rust does not - and vice versa, I’m not saying one is better than the other, just different. I rather like that they’re different and I would encourage people to learn more about how Python’s objects and types work under the hood than to try to wallpaper over it with syntax from other languages. You’ve noticed Enum and ABC and dataclasses are not “first class” syntactic concepts in Python - that’s because the existing object system is powerful enough to add these features as libraries instead of by extending the base language. Besides these standard library modules there is a rich ecosystem of packages that exist (I would argue) only because of what metaclasses can do. And this for example:

You can do this right now. It would be some work to implement, but there’s no reason at all you couldn’t. You might have some difficulty getting type checkers to like it :wink: but Python doesn’t mind at all.

One last thing, regarding abstract method definitions. Python doesn’t just have a statement for “I’m not doing anything here” (pass). It has one for “this isn’t implemented” too, in two different flavors. That second one gets used in type stubs all the time, but it’s perfectly valid to use it in your abstract method bodies too.

4 Likes

My understanding of the situation is that the way Python’s class is powerful is almost the same way as AST is powerful: as you repeat, this is more about the object system (some hashmap or tree-like structure with labels) than the class system. By classes here, I’m thinking of Haskell’s typeclasses or Rust’s traits. (You don’t have to tell me that Python classes are also objects, hence metaclasses…)

What I’ve been discussing here is how to elicit a necessary and sufficient syntax for modelling data, while making type checkers like it too. (Why did some of you think this post was meant for the general Ideas channel rather than technically Typing one by the way?)

In abstract classes, the idea of default implementation - you can OPTIONALLY ADD an implementation body part - is preferable to the idea that you’re normally supposed to fill the function body part, but can OPTIONALLY OMIT it by placing special markers such as pass or NotImplementedError.

Because from reading the OP there was nothing in it specific to typing and no suggestion in the text that it was.

1 Like

Fair enough. I also like a good algebraic type system for domain modeling. It’s fine to have this goal, but please be aware of (1) the cost of adding syntax to a language - these are seldom as trivial as they look and can have large second order effects, and (2) the tension between type annotations and the “actual” type system. Annotations are fundamentally optional and adding things to the core language solely to help type checking is… highly debatable and for good reason.

Maybe we’ll just have to agree to disagree here, as there’s a subjective component to this. (I disagree that optionally adding a body is preferable to optionally omitting it - in fact I don’t mind either way.) But Python does not separate declaration from definition. In C++ you can omit the body of a virtual method by declaring it without defining it. Python does not even have this concept. You could argue for adding some syntax sugar, if you dislike typing “” so much - but fundamentally everything in Python is declared by being defined.

Edit: re: traits and typeclasses

Python generally uses multiple inheritance for this. “Mix-in” classes can add arbitrary methods and attributes to classes, call methods defined on the class they are added to, and introspect at runtime to add behavior. Yes, this is less strictly defined than a trait system - but that’s kind of the point. :wink: Just duck type it! :duck:

I feel that at some point, adding more and more static type system features to Python would create a language that is… not Python. It would be something else, and maybe be a good language, but a significant departure from Python’s core. At the end of the day everything happens at runtime and asks forgiveness not permission. Sometimes that’s good and sometimes it’s not, but IMO it’s one of the things that make Python what it is.

2 Likes