-
Notifications
You must be signed in to change notification settings - Fork 0
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
The abstract-final pattern and generics: should AbstractState
be eliminated?
#24
Comments
A problem that may be directly related: I had been thinking that a given final subclass of For example, However, A related problem: loss functions are passed the history of states over a trial, as a I have tried to use protocols to solve this: from typing import Protocol
from feedbax.loss import AbstractLoss
from feedbax.state import CartesianState
from feedbax.task import AbstractTaskTrialSpec
class HasEffectorState(Protocol):
effector: CartesianState
class HasMechanicsEffectorState(Protocol):
mechanics: HasEffectorState
class EffectorFooLoss(AbstractLoss):
...
def term(
self,
states: HasMechanicsEffectorState,
trial_specs: AbstractTaskTrialSpec,
) -> Array:
... Clearly this gets ugly pretty quickly. My current understanding is that:
|
This issue also applies to |
I've switched |
One related pyright error that doesn't make sense to me is:
This refers to the appearance of Lines 517 to 530 in a6b68bd
The type variable appears in both the arguments and the return -- I don't see why it is meaningless. (The return type should be a tuple of two PyTrees, though, since we can modify multiple tasks/models now.) |
I've switched Line 39 in b73dfb8
This allows Line 77 in b73dfb8
|
I just revisited this issue and was confused why I had thought that the abstract-final pattern implied that (Perhaps I was confused about the "need" for contravariance in the Note how Equinox determines whether a module is abstract, when performing def _is_abstract(cls):
return (
_is_force_abstract[cls]
or len(cls.__abstractmethods__) > 0
or len(cls.__abstractvars__) > 0
or len(cls.__abstractclassvars__) > 0
) Thus a totally empty class does not count as abstract, since it contains no abstract attributes or methods. But this is currently the way that Lines 29 to 36 in 0dbaffb
I suppose Equinox's strategy makes sense, since this isn't really an abstract class. It's more like an alias for |
This should be obviated if we replace the staged model approach with a DAG approach (#28), so I'm tagging this issue as "invalid". In particular, state containers will behave more generally and I think we won't be typing them as explicitly from within the library. |
If we follow the abstract-final pattern strictly, such as by setting
strict=True
when subclassingequinox.Module
, thenAbstractState
would need to include anAbstractVar
for every field that appears in every subclass;AbstractState
would need to implement every one of those fields.Clearly this doesn't make sense, since different types of state PyTrees usually don't share any fields.
This might also be a reason we should expect to see issues with generic typing of
AbstractModel[StateT]
, whereStateT
is bound toAbstractState
. However, my understanding is that type invariance should be preserved as long as we respect the abstract-final pattern when subclassing whicheverAbstractState
subclass is ultimately used as the type argument for a final subclass ofAbstractModel
.I suspect the solution is:
StateT
with a type variable bound toequinox.Module
;AbstractFooState
) that inherit fromequinox.Module
, for different final subclasses ofAbstractModel
. Each of these should respect the abstract-final pattern.The text was updated successfully, but these errors were encountered: