Here is what works for me to write higher order typed Python:
- Use class with __call__ instead of lambdas or functions (aka explicitly specify each closure).
- Favor constraining types with Callable, ParamSpec and Concatenate (not with class constructions).
- Replace *args by immutable list (Tuple[a,Tuple[b, ...) when ParamSpec fails to scale.
- Replace **kwargs with object using @overload with Literal typed arguments (sadly TypedDict seems not to support generic typing) .
- Use Generic to “store” multiple polymorphic types
- Use Protocol to separate semantic tiers of types (e.g. ownership in a smart contract)
- No nested functions, lambdas or classes.
- Use cast to type "unmanaged" types (e.g. eval)
- Use phantom typed "dummy" arguments (and type casts) to get around "wrong direction" type dependencies
- Visual Code's Pyright works. Early 2021, pycharm fails.
The untyped application scope is a broader Python 3 stack. Note this is 3.10 Python typing (e.g. ParamSpec).
Not all is easy. Deeply typed Python seems to be magnitudes more expensive to write than "classical" Python. Also, Union types may be needed to "break out" from "tight" type relations. My current feeling is that one is sometimes forced to introduce Unions to allow complex trace joins and bifurcation, to then need to add additional layers of type constraints to control the generality of these Unions. All of this needs to be done with careful "locking" of types. Not the best of situations!
Python is known to be a dynamically typed language, which is ok, as static typing is more of a luxury than a necessity. I learned the above this 2020-2021 Xmas holiday writing lens/optic like python code. Initially with no types, fully lambda centric. Then I thought: let’s try to make it typed in Python!
All original content copyright James Litsios, 2021.
No comments:
Post a Comment