{"action":"created","issue":{"url":"https://api.github.com/repos/ambv/typehinting/issues/45","labels_url":"https://api.github.com/repos/ambv/typehinting/issues/45/labels{/name}","comments_url":"https://api.github.com/repos/ambv/typehinting/issues/45/comments","events_url":"https://api.github.com/repos/ambv/typehinting/issues/45/events","html_url":"https://github.com/ambv/typehinting/issues/45","id":54659121,"number":45,"title":"Convertible-to constraints","user":{"login":"abarnert","id":1808250,"avatar_url":"https://avatars.githubusercontent.com/u/1808250?v=3","gravatar_id":"","url":"https://api.github.com/users/abarnert","html_url":"https://github.com/abarnert","followers_url":"https://api.github.com/users/abarnert/followers","following_url":"https://api.github.com/users/abarnert/following{/other_user}","gists_url":"https://api.github.com/users/abarnert/gists{/gist_id}","starred_url":"https://api.github.com/users/abarnert/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abarnert/subscriptions","organizations_url":"https://api.github.com/users/abarnert/orgs","repos_url":"https://api.github.com/users/abarnert/repos","events_url":"https://api.github.com/users/abarnert/events{/privacy}","received_events_url":"https://api.github.com/users/abarnert/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2015-01-17T11:31:20Z","updated_at":"2015-01-17T17:28:42Z","closed_at":null,"body":"Many type constraints in both stdlib and external functions are not of the form \"`x` is an instance of type `T` (or a subtype thereof)\", but of the form \"`x` is something that can be converted to type `T` (or a subtype thereof)\".\r\n\r\nIn fact, the de-facto dynamic type system in Python is in some ways closer to C++'s static type system, which is built all around conversions, than to a more traditional static type system. But in C++, convertibility is explicitly definable.<super>\\*</super>\r\n\r\nThe problem is that there are a variety of kinds of type-conversion systems in use, many of them user-extensible, including:\r\n\r\n * **Dunder-method protocols**, like \"`x.__index__` exists\". This one's *almost* easy—an ABC with a `__subclasshook__` takes care of it. Except that the static type checker then has to understand ABCs besides the ones that are built in, down to the level of being able to call their `__subclasshook__` methods.\r\n * **Construction**: `T(x)` would succeed. If there were some way to specify \"any type that matches the second argument type of `T.__new__` and `T.__init__`\", but that isn't.\r\n * **Alternate construction**: Some function `f(x)` (or classmethod `T.f(x)`) would succeed. (For example, I think the only hard part of defining NumPy's \"array-like\" is that it's a `Union` of a bunch of things—`ndarray`, the NumPy scalar types, native Python numbers, and anything for which `np.array(x)` is allowed.)\r\n * **Delegation to methods**. Besides the obvious, this includes delegation to methods which are explicitly intended to be overridden in subclasses. For example, `JSONEncoder.encode` takes any type that `self.default` takes.\r\n * **Delegation to attributes**: `T(x._as_parameter_)` would succeed. This can also include callables; the return type of a `ctypes` function is `f.restype` if it's not callable, but if if it's a `Callable[[X], Y]` it's `Y`.\r\n * **Registries**. Consider how various SQL query libraries handle \"can be stored in a `REAL` column\" by, e.g., looking in a map to see if `register_adapter(type(x), REAL)` has been called.\r\n\r\nNot all of these cases need to be statically type-checked, of course. (And `ctypes` seems like it pretty obviously could/should be a special case, at least in a v2 proposal.) But I think things like `MySequence.__getitem__` or `np.add` are the kinds of things people might want to type. And it would be nice to be able to declare that my function can take anything convertible to, say, `ip_address`, instead of having to manually `Union` together `ip_address` and its constructor's argument type. And so on.\r\n\r\n<sub>\\* It's actually quite a mess. An implicit conversion (like passing an argument to a function) you have both built-in rules (subtype conversion for reference or pointer types, const qualification, traditional C coercions, etc.) and user-defined (either `x.operator T()` exists and is accessible, or there's an unambiguous overload for `T(x)` and it's accessible and not explicit) ones, and can have two conversion steps as long as only one of them is user-defined; an explicit conversion (like assigning an initial value to a variable declaration) has different rules. But at least it's a clearly-documented mess of unbreakable rules, and if you want to extend it with, say, a registry of adapters, you have to write templates that do that at compile time in terms of the rules.</sub>"},"comment":{"url":"https://api.github.com/repos/ambv/typehinting/issues/comments/70375448","html_url":"https://github.com/ambv/typehinting/issues/45#issuecomment-70375448","issue_url":"https://api.github.com/repos/ambv/typehinting/issues/45","id":70375448,"user":{"login":"gvanrossum","id":2894642,"avatar_url":"https://avatars.githubusercontent.com/u/2894642?v=3","gravatar_id":"","url":"https://api.github.com/users/gvanrossum","html_url":"https://github.com/gvanrossum","followers_url":"https://api.github.com/users/gvanrossum/followers","following_url":"https://api.github.com/users/gvanrossum/following{/other_user}","gists_url":"https://api.github.com/users/gvanrossum/gists{/gist_id}","starred_url":"https://api.github.com/users/gvanrossum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gvanrossum/subscriptions","organizations_url":"https://api.github.com/users/gvanrossum/orgs","repos_url":"https://api.github.com/users/gvanrossum/repos","events_url":"https://api.github.com/users/gvanrossum/events{/privacy}","received_events_url":"https://api.github.com/users/gvanrossum/received_events","type":"User","site_admin":false},"created_at":"2015-01-17T17:28:42Z","updated_at":"2015-01-17T17:28:42Z","body":"Hm... I think the first three are ways of saying \"the type must be acceptable as the argument to the XXX builtin\", right? E.g. I could imagine something that takes either an iterable of pairs or a mapping, and passing it to a dict. But then it just comes down to being able to spell the argument of dict() in the type system, so I don't think something new is needed. (You may have a problem spelling the XXX argument type, but we should face such cases head on.)\r\n\r\nJSON is such a dynamic mess that I don't want to worry about it yet. (Basic JSON can be spelled easily as something four-way recursive or such, but the encoder/decoder machinery makes this completely untractable.)\r\n\r\nDelegation to attribute sounds like something that structural subtyping could solve; we're not doing that in this PEP but there might be a follow-up PEP. Perhaps this can also handle certain registries. See #11. (It's closed because we decided not to do it in PEP 484, but maybe it should remain open to focus the discussion for a future PEP?)"}}