Skip to content

Instantly share code, notes, and snippets.

@Gobot1234
Last active June 23, 2023 17:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gobot1234/71e4d948b02c7a6f04cf484455c6d971 to your computer and use it in GitHub Desktop.
Save Gobot1234/71e4d948b02c7a6f04cf484455c6d971 to your computer and use it in GitHub Desktop.

PEP 9999 - Subscriptable functions

Abstract

This PEP proposes making FunctionType, MethodType, BuiltinFunctionType and BuiltinMethodType instances subscriptable for typing purposes. Doing so allows for specialisation in complex, hard-to-infer situations for type checkers where bi-directional inference (which allows for the types of parameters of anonymous functions to be inferred) is insufficient.

Motivation

Currently, it is not possible to infer certain situations for generic functions where unspecialised literals are involved

def make_list[T](*args: T) -> list[T]: ...
reveal_type(make_list())  # type checker cannot infer T

Making instances of FunctionType subscriptable would allow for this constructor to be typed

reveal_type(make_list[int]())  # type is list[int]

Currently you have to use an assignment to allow for this to be type-able

x: list[int] = make_list()
reveal_type(x)  # type is list[int]

but this code is unnecessarily verbose taking up multiple lines for a simple function call.

def factory[T](func: Callable[[T], Any]) -> Foo[T]: ...

reveal_type(factory(lambda x: "Hello World" * x))  # type checker cannot infer T (again)

If functions were subscriptable, however, the following could be performed

reveal_type(factory[int](lambda x: "Hello World" * x))  # type is Foo[int]

Currently, with unspecialised literals, it is not possible to determine a type for situations similar to:

def foo[T](x: list[T]) -> T: ...
reveal_type(foo([]))  # type checker cannot infer T (yet again)
reveal_type(foo[int]([]))  # type is int

It is also useful to be able to specify in cases in which a certain type must be passed to a function beforehand

words = ["hello", "world"]
foo[int](words)  # Invalid: list[str] is incompatible with list[int]

Allowing subscription makes functions and methods consistent with generic classes where they weren't already. Whilst all of the proposed changes can be implemented using callable generic classes, syntactic sugar would be highly welcome.

This proposal also opens the door to monomorphisation/reified types

Specification

FunctionType, MethodType, BuiltinFunctionType and BuiltinMethodType should implement __getitem__ to allow for subscription at runtime and return an instance of types.GenericAlias with __origin__ set as the callable and __args__ as the types passed.

Type checkers should support subscripting functions and understand that the parameters passed to the function subscription should follow the same rules as a generic callable class.

Setting __orig_class__

Currently, __orig_class__ is an attribute set in GenericAlias.__call__ to the instance of the GenericAlias that created the called class e.g.

class Foo[T]: ...

assert Foo[int]().__orig_class__ == Foo[int]

Currently, __orig_class__ is unconditionally set, however, to avoid potential erasure of any created instances, this attribute should not be set if __origin__ is an instance of any of the aforementioned types.

The following code snippet would fail at runtime without this change as __orig_class__ would be bar[str] and not Foo[int]. We think this is a bug.

def bar[U]():
    return Foo[int]()

assert bar[str]().__orig_class__  is Foo[int]

Reference Implementation

The runtime changes proposed can be found here https://github.com/Gobot1234/cpython/tree/function-subscript

Acknowledgements

Thank you to Alex Waygood and Jelle Zijlstra for their feedback on this PEP and Guido for some motivating examples.

Copyright

This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.

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