Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static typing #647

Open
davidbrochart opened this issue Mar 8, 2021 · 10 comments
Open

Static typing #647

davidbrochart opened this issue Mar 8, 2021 · 10 comments

Comments

@davidbrochart
Copy link
Contributor

traitlets' types are used to perform run-time checks. Would adding type annotation on them make sense?
It seems that we have some information that we could use to potentially find bugs ahead of time. For instance, should a Bool be statically typed as a bool? I don't even know if that makes sense 😄
One problem I'm seeing (for instance in https://github.com/jupyter/nbclient) is that adding type annotations on traits doesn't seem to have any effect.

@rmorshea
Copy link
Contributor

rmorshea commented Mar 8, 2021

That'd definitely be a possibility, and the way you'd want to type this is via generics:

T = TypeVar("T")

class TraitType(Generic[T]):
    def __get__(self, obj, cls) -> T:
        ...
    def __set__(self, obj, val: T) -> None:
        ...

class Bool(TraitType[bool]):
    ...

With this approach you wouldn't actually have to add type annotations to your classes - you'd basically just get it for free.

@davidbrochart
Copy link
Contributor Author

Interesting, thanks Ryan. I'm going to try this out with a simple case and report back on how it goes.

@rmorshea
Copy link
Contributor

rmorshea commented Mar 8, 2021

I think this'd be a pretty useful feature for Traitlets since it's non-intrusive, but will have a broad positive impact on everyone downstream that shouldn't require significant code changes that will probably find a lot code smells and bugs!

Also, depending on how you see things, this addition might constitute a breaking change, because users running MyPy might experience errors in their test suite even though the Traitlets API will technically have remained unchanged. So my inclination is to say this would require a Traitlets 6.0 release. Thoughts @SylvainCorlay?

@davidbrochart
Copy link
Contributor Author

Actually it brings up some interesting questions, for instance, how can allow_none=True be supported? Since this would translate into the Optional[...] static typing annotation, how to apply this conditionally to the allow_none value?

@rmorshea
Copy link
Contributor

rmorshea commented Mar 10, 2021

It definitely gets a little tricky, but I think you could solve that with typing.overload:

T = TypeVar("T")

class TraitType(Generic[T]):

    @overload
    def __new__(cls, allow_none: Literal[True]) -> TraitType[Optional[T]]:
        ...
    @overload
    def __new__(cls, allow_none: Literal[False]) -> TraitType[T]:
        ...
    def __new__(cls, allow_none: bool = False) -> None:
        ...

@mangecoeur
Copy link

+1 to this idea, after finding that while trying to keep both traitlets and PyCharm type hints happy, I ended up writing Java :

distance: float = Float()

😆

@davidbrochart
Copy link
Contributor Author

Actually, I'm thinking more and more that we should do things the other way around: not add static typing to a dynamic typing library, but add dynamic typing to static typing. Oh but wait, that's what Pydantic does 😄

@mangecoeur
Copy link

You're probably right, but what to do about traitlet features like observer pattern and data binding in ipywidgets? Could they work with the pydantic approach? I don't know how that works internally perhaps it's not as disruptive as it seems... what I do know is it's incredibly useful!

@davidbrochart
Copy link
Contributor Author

I think we could implement the observer pattern on top of pydantic, see https://github.com/davidbrochart/pyceptive. For ipywidgets, I think it would be feasible too.

@davidbrochart
Copy link
Contributor Author

The new declare library uses type annotations:

from declare import Declare

class Foo:
    val0 = Declare[int](0)
    val1 = Declare[str]("")

Maybe worth looking at?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants