-
-
Notifications
You must be signed in to change notification settings - Fork 1
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
Remove defaults #3
Remove defaults #3
Conversation
I think making this an option sounds reasonable!
Yeah, much like
I'm not sure what you mean by this! Which other Python types might we also wish to adjust? |
I'll get to work then :) |
By the way, just looking at this: why do we not support/special-case jaxtyping aliases that are defined as a union, like For instance, I find the long enumeration of things that |
Good point about namedtuples. I feel like that for that we probably do want to include defaults, since they're more tuple than class? Not sure. I basically never use them, FWIW. As for handling pretty unions: this was on my mind as well, but I didn't have an immediate nice way to handle it here. Two possible options:
I definitely wouldn't try to handle it here, otherwise we'll pick up a circular dependency jaxtyping<>wadler_lindig! |
I never use them either. They're used extensively in blackjax to define the sampler states, but I don't think they have default values in there. That is pretty much the only place I come across them. I opted to ignore these for now, if there is ever a request then we can add them. As for the PR: showing defaults is now turned off by default, but fields that have default values will still be shown if that value has been tweaked. As a concrete example, if an optimistix solver/search/descent defaults to a As for the unions: I'll get back to you on that! |
All done :) |
wadler_lindig/_definitions.py
Outdated
or field.default is dataclasses.MISSING | ||
or field.default != getattr(obj, field.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think just the second check is needed here? If field.default is dataclasses.MISSING
then we'll definitely also trigger the next check.
FWIW btw just a plain getattr
isn't safe if the dataclass is only partially initialised:
@dataclass
class Foo:
x: int
def __init__(self):
wl.pformat(self)
self.x = 4
(Not sure if we have a test for this, I just know to look out for it.)
This is the reason for the _WithRepr("<uninitialised>")
in the other getattr
above. In particular this scenario arises when runtime-type-checking inputs to dataclass initialisation.
I'm also thinking that the !=
check may fail if we compare nonscalar numpy arrays, for which __eq__
is overridden. It has the potential to be slightly more verbose but I think an is
check might be safer here.
Putting this together, maybe what we need here is:
uninitialised = _WithRepr("<uninitialised>")
objs = []
for field in dataclasses.fields(obj):
if field.repr:
value = getattr(obj, field.name, uninitialised)
if not (kwargs["hide_defaults"] and value is field.default):
objs.append((field.name.removeprefix(type_name), value))
objs = named_objs(objs, **kwargs)
Or something like that anyway.
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented, this does everything it should! I did not know about the getattr footgun, thanks for pointing it out!
Just saw that this does not catch the case where the default is only defined in @dataclasses.dataclass
class Foo:
array: np.ndarray
def __init__(self, array: np.ndarray = np.ones((3, 4))):
self.array = array I'll figure this out. |
Hm not sure if this can be done reliably. The parameter name in |
Do you have an idea for dealing with this? The only thing that I can come up with is:
and that seems very, very clunky! |
I hope you enjoyed the sunshine today :) Honestly, I'm not sure we can handle it. Someone could do completely arbitrary things inside Maybe there's an elegant way for the author of the dataclass to opt-in to this behaviour instead? For example something like this would still work (and could maybe still be improved upon): _x_default = 4
@dataclass
class Foo:
x: int = _x_default
def __init__(self, x=_x_default):
self.x = x |
I think that makes sense. Should this be an info box in the documentation or something in the FAQ? |
And thanks, I did! |
I'm not sure how common this is at the moment / if this is something we should advertise. I'm guessing you're thinking about this because you found some use cases in the Equinox ecosystem that would make this useful? |
In optimistix, defaults for the norms are defined in this way at the solver level, and verbose options too. Edit: e.g. here: |
I think the most pragmatic way forward here is to do our own workarounds in optimistix where helpful, and leave this thread here as a minimal documentation of the issue? |
…defined at the level of dataclass fields
Agreed. We could probably write an eqxi decorator that copies defaults from |
Agreed on the second point, and I'll put the first point on my list of not-super-urgent TODOs :) |
As you can see, this is a tiny tweak.
However, I'm wondering if removing defaults is ...itself a reasonable default? In other words, if this should be an option that defaults to
True
, same asshort_arrays
. And if it should then be implemented for other Python types as well.(For this reason, I have not updated the tests yet.)
PS: With this tweak, the optimistix documentation for
IndirectLevenbergMarquardt
looks like thisinstead of like this:
x-ref: patrick-kidger/optimistix#109 (comment)