dag / braindump

Ideas and Experiments
1 stars 1 forks source link

Interfaces #8

Open dag opened 13 years ago

dag commented 13 years ago

Related to #5, #6 and #7, but only focusing on interfaces.

Terms and Naming Conventions

The longer ones become an annoyance once you start putting them in function annotations in particular. You want the signature to be short and concise, preferably fitting on one line. Also repeating "Interface" or such for every type quickly starts to sound like a broken record.

The stdlib doesn't seem completely self-consistent anyway:

The general trend though seems to be a preference recently for plain nouns with mixed abstract/concrete methods.

API

class PersonType (metaclass=Interface):

    # typed attribute
    first_name = str

    # untyped attribute using the Ellipsis constant
    last_name = ...

    # special method for asserting invariants about the object
    def __invariant__(self):
        assert self.first_name or self.last_name, 'must set at least one of the names'

    # abstract methods with type annotations
    def greet(self, person: PersonType) -> str:
        # normally "PersonType" isn't available yet when the "def" executes;
        # a workaround could be to have a special "This" type á la Traits,
        # although it could be aliased to the class name using __prepare__ in the metaclass

        # contractual pre-condition assertions in the abstract method body
        assert person is not self, 'not greeting myself, silly'

        # generator coroutines for contractual post-conditions
        res = yield
        assert len(res) < 100, 'i am not a blabbermouth dammit'

# implementations simply inherit from the interface.
# if the implementation is complete, issubclass will be true for the interface,
# if not, instance creation will fail
class Person (PersonType):
    ...

If a class conforms to an interface but does not inherit from it, issubclass should be false but there could be a custom API for checking conformance beyond inheritance. Perhaps calling an interface with an object could return the object if it is conforming, and otherwise raise an error, forming a simplistic adapter.

dag commented 13 years ago

Conflicting ideals:

This begs the question if issubclass(ExtendedPersonInterface, PersonInterface) should be true, which would be expected from the inheritance, but inconsistent with the use of issubclass for validating interfaces against concrete implementations.

Possible solution: use a separate, custom predicate for checking if an interface extends on another, with issubclass returning False. Existing interface implementation such as zope.interface seems to be doing this the other way around (custom predicates for checking if a concrete class implements an interface etc) but at least in part this might be because __subclasscheck__ et al didn't exist until recently.

dag commented 13 years ago

Maybe the Interface metaclass should construct a class with a different metaclass (possibly one that enforces the interface), and extending an interface should require repeating the Interface metaclass:

class ExtendedPersonType (PersonType, metaclass=Interface):
    ...
dag commented 13 years ago

Implementations of an extending interface should probably not be considered implementations of the extended interface, unless explicitly inheriting both. Maybe there could be a separate syntax for saying that an extending interface implies the extended interface as well (including its checks, even if the extension overrides and conflicts).

dag commented 13 years ago

Maybe "contract" is a better name, so as to not confuse things with type classification which in Python may also include ABCs or plain base classes.

dag commented 13 years ago

Parametric types / generics:

# A..Z could be constants in a module
class Mapping (metaclass=Interface[K:V]):

    def __getitem__(self, key: K) -> V:
        ...

assert Mapping[str:int](dict(a=1, b=2))