pyglove icon indicating copy to clipboard operation
pyglove copied to clipboard

Questions about `pg.symbolize`, `pg.members` and compatibility with pyright

Open jvican opened this issue 2 years ago • 1 comments

I've noticed that we have both pg.symbolize and pg.members to do effectively the same thing, symbolizing a class, albeith in different ways. The benefits of pg.symbolize is that one can use it around a regular class definition and add type hints for all parameters, whereas pg.members creates all of that under the hood given the list of arguments.

Using pg.members is nice as one can also provide the documentation for each parameter and express the nested structure of parameters. But it has the shortcoming that typecheckers like pyright cannot know what the types of the parameters are because they are created on the fly by the annotation.

I have two questions:

  1. When should we prefer pg.symbolize over pg.members? Most of the usages I've seen of pg.symbolize in the notebooks is to wrap around existing classes but it's also handy as an alternative to pg.members as far as I understand?

  2. What is the story between pyright compatibility in pyglove? I know the dynamic capability of pg.members doesn't bode well with typings, but is there a way we could generate pyglove type stubs dynamically? (In this context, ParamSpec seems relevant.) This way one could use pg.members and benefit from type checking when using the parameters.

jvican avatar Sep 23 '22 20:09 jvican

Excellent questions.

  1. When should we prefer pg.symbolize over pg.members?

A short answer is: use pg.symbolize when symbolizing an existing class that you cannot modify their source code; and use pg.members (and subclassing pg.Object) when you have full control on the class definitions.

Here is a longer version: Both pg.symbolize and pg.members w/ subclassing pg.Object add symbolic programmability to Python classes. pg.symbolize inject such capabilities into existing classes through multi-inheritance , which tries to avoid clashing with the original classes' behavior (e.g. __eq__, __hash__, attribute access) as much as possible. To do so, PyGlove adds minimum modifications to the existing classes's interface by default. On the other hand, we assume that users are mindful and have full control on the class behaviors when they subclass pg.Object, thus we expose all the symbolic interfaces by default (like <object>.<attr_name>, symbolic __eq__, clone, and etc.).

  1. What is the story between pyright compatibility in pyglove?

Similar as pytype, pyright does static analysis on the Python files, while PyGlove type check is done at run time. so I don't think pytype/pyright can easily figure out the outcome of pg.members without handwritten rules (like pytype's special handling for dataclass). So I guess that we might eventually solve this through the other way around, by making pg.members more pytype/pyright annotation friendly. I haven't explored this path yet, but it might be possible to converge the difference using dataclass-like annotation as a bridge.

daiyip avatar Sep 24 '22 01:09 daiyip