Support for sealed classes
Hello typing-sig, I wanted to suggest adding support for sealed classes to the python type system. What are sealed classes? Sealed classes are classes of which all subclasses are known statically. This is usually accomplished by requiring all subclasses to be created in the same file/module as the parent class. What languages support sealed classes? Sealed classes are supported by C#, Kotlin and starting with Java 15 even by Java. What can sealed classes be used for? Sealed classes are useful for checking the exhaustiveness of if/else towers and (python 3.10) match statements for example when working with abstract syntax trees. Take a look at this example: ``` class Node(ABC): ... class Value(Node): ... class BinOp(Node): ... class UnOp(Node): ... n: Node match n: case Value(v): ... case BinOp(n1, op, n2): ... case UnOp(op, n1): ... # Can we statically check if all cases are handled? ``` Sealed classes in python: My suggestion would be to add a new class "Sealed" to typing and typing_extensions that can be inherited from: class Node(Sealed): ... Type checkers should make sure that no subclasses of Node are created in modules other than the module Node was created in and should treat sealed classes as abstract classes. Additionally they can keep track of all the subclasses of Node do implement exhaustiveness checking. The runtime implementation of sealed could also prevent non-typechecked code from creating additional subclasses: ``` class Sealed(ABC): def __init_subclass__(cls): for c in cls.__mro__: if Sealed in c.__bases__ and c.__module__ != cls.__module__: raise RuntimeError('Sealed class can\'t be subclassed from another module. Sealed in module "{}".'.format(c.__module__)) ``` Final example: a.py: ``` from typing import Sealed class Base(Sealed): pass class A(Base): pass class B(A): pass ``` b.py: ``` from a import Base, A class C(Base): # raises an exception pass class D(A): # also raises an exception pass ``` Adrian Freund
See also this section of PEP 622 (Structural Pattern Matching), which proposed a `@sealed` class decorator: https://www.python.org/dev/peps/pep-0622/#sealed-classes-as-algebraic-data-t... To avoid losing focus, that whole static typing section was dropped when the proposal was rewritten as PEPs 634, 635, and 636. I would definitely support one or more PEPs that formalize the static typing behavior of match statements and reintroduce sealed classes. I'm still undecided on runtime-validation and decoration vs. inheritance, though... are there potential use-cases where inheritance wouldn't be possible, or where we wouldn't actually want runtime-validation? If we go the ABC route, it may make more sense to check `isinstance(c, Sealed)` instead of `Sealed in c.__bases__`. That would allow imported classes (perhaps from C extensions) to be later registered as sealed. We could even go so far as to define `sealed = Sealed.register` if we wanted to use decoration instead of inheritance. Brandt
Decorators are better because they combine, unlike metaclasses. On Thu, May 20, 2021 at 11:45 Brandt Bucher <brandtbucher@gmail.com> wrote:
See also this section of PEP 622 (Structural Pattern Matching), which proposed a `@sealed` class decorator:
https://www.python.org/dev/peps/pep-0622/#sealed-classes-as-algebraic-data-t...
To avoid losing focus, that whole static typing section was dropped when the proposal was rewritten as PEPs 634, 635, and 636. I would definitely support one or more PEPs that formalize the static typing behavior of match statements and reintroduce sealed classes.
I'm still undecided on runtime-validation and decoration vs. inheritance, though... are there potential use-cases where inheritance wouldn't be possible, or where we wouldn't actually want runtime-validation?
If we go the ABC route, it may make more sense to check `isinstance(c, Sealed)` instead of `Sealed in c.__bases__`. That would allow imported classes (perhaps from C extensions) to be later registered as sealed. We could even go so far as to define `sealed = Sealed.register` if we wanted to use decoration instead of inheritance.
Brandt _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: guido@python.org
-- --Guido (mobile)
It seems worth noting that you can already use static types that are de facto sealed using @final (PEP 591): @final class Value(Node): ... @final class BinOp(Node): ... @final class UnOp(Node): ... AnyNode = Union[Value, BinOp, UnOp] Clearly the Sealed proposal would be easier to read and write, and probably has other advantages I haven't fully thought through (e.g. having an actual runtime class, or doing runtime enforcement unlike @final).
+1 for static type checking and avoiding inheritance in the syntax. My interest comes from working on transpiling python, including sealed classes to other statically typed languages. I've used syntax such as this in one of my earlier projects with good success: ``` @sealed class Node: EXPRESSION = Expression STATEMENT = Statement @sealed class Expression: NAME = Name OPERATION = Operation @sealed class Statement: ASSIGNMENT = Assignment ``` Will need some metadata (another decorator?) to distinguish these class members from other class members that have nothing to do with sealed classes. This way, the static checker knows how to compute the exhaustive list of permitted classes when checking a match statement. -Arun On Thu, May 20, 2021 at 8:07 PM <asafspades@gmail.com> wrote:
It seems worth noting that you can already use static types that are de facto sealed using @final (PEP 591):
@final class Value(Node): ... @final class BinOp(Node): ... @final class UnOp(Node): ... AnyNode = Union[Value, BinOp, UnOp]
Clearly the Sealed proposal would be easier to read and write, and probably has other advantages I haven't fully thought through (e.g. having an actual runtime class, or doing runtime enforcement unlike @final). _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: arun.sharma@gmail.com
On Fri, May 21, 2021 at 9:38 PM Arun Sharma <arun@sharma-home.net> wrote:
+1 for static type checking and avoiding inheritance in the syntax.
My interest comes from working on transpiling python, including sealed classes to other statically typed languages.
I've used syntax such as this in one of my earlier projects with good success:
``` @sealed class Node: EXPRESSION = Expression STATEMENT = Statement
@sealed class Expression: NAME = Name OPERATION = Operation
@sealed class Statement: ASSIGNMENT = Assignment ```
A variant of this syntax and a proof of concept implementation built on top of adt [1] library: https://bit.ly/3upzGSc The upstream ADT library has some mypy plugins. Would be great if other type checkers supported something like this out of the box. -Arun [1] https://github.com/jspahrsummers/adt
On Fri, May 28, 2021 at 11:01 AM Arun Sharma <arun@sharma-home.net> wrote:
A variant of this syntax and a proof of concept implementation built on top of adt [1] library:
The upstream ADT library has some mypy plugins. Would be great if other type checkers supported something like this out of the box.
-Arun
This syntax is now supported in the python -> rust transpiler. Test case in this commit: https://github.com/adsharma/py2many/pull/314 I also have a version of Richard's benchmark using this syntax transpiled to rust. The generated code doesn't compile yet. The only thing missing is a plan for some syntax in python that can be mapped to pattern matching as an expression in rust. Please get in touch with me offline if you're interested in discussing it further. -Arun
On Fri, Jun 4, 2021 at 5:42 PM Arun Sharma <arun@sharma-home.net> wrote:
On Fri, May 28, 2021 at 11:01 AM Arun Sharma <arun@sharma-home.net> wrote:
A variant of this syntax and a proof of concept implementation built on top of adt [1] library:
The upstream ADT library has some mypy plugins. Would be great if other type checkers supported something like this out of the box.
-Arun
This syntax is now supported in the python -> rust transpiler. Test case in this commit:
Also supported in the python -> smt transpiler as of: https://github.com/adsharma/py2many/pull/443/files Some links about python and SMT: https://ericpony.github.io/z3py-tutorial/guide-examples.htm http://smtlib.cs.uiowa.edu/ Compare: ; smt-lib2 (declare-datatypes ((IntList 0)) (((None) (intlistnonempty (first Int) (rest IntList))))) or # z3py # Declare a List of integersList = Datatype('List')# Constructor cons: (Int, List) -> ListList.declare('cons', ('car', IntSort()), ('cdr', List))# Constructor nil: ListList.declare('nil') The sealed classes feel a lot more natural to write. -Arun
účastníci (5)
-
Adrian Freund
-
Arun Sharma
-
asafspades@gmail.com
-
Brandt Bucher
-
Guido van Rossum