-
Notifications
You must be signed in to change notification settings - Fork 21
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
implement and document getfields #54
Conversation
Codecov Report
@@ Coverage Diff @@
## master #54 +/- ##
============================================
- Coverage 100.00% 34.18% -65.82%
============================================
Files 3 3
Lines 113 117 +4
============================================
- Hits 113 40 -73
- Misses 0 77 +77
Continue to review full report at Codecov.
|
@rafaqz if you don't have time for a full review, can you maybe just give a quick feedback on whether you think we should have a |
I don't totally understand how this interacts with |
The relation between
Is this the kind of answer you were looking for? |
Should it maybe be |
src/constructorof.md
Outdated
@@ -32,17 +32,17 @@ julia> constructorof(S)(1,2,4) | |||
ERROR: AssertionError: a + b == checksum | |||
``` | |||
Instead `constructor` can be any object that satisfies the following properties: | |||
* It must be possible to reconstruct an object from the `NamedTuple` returned by | |||
`getproperties`: | |||
* It must be possible to reconstruct an object from the `Tuple` returned by |
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.
Shouldn't this still be getproperties
? I'm a little confused about how this is going to work. We have getproperties
methods that let us ignore undefined
fields and provide multiple constructorof
implementations depending on the number of fields.
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.
The idea was, that constructorof
should accept all private + public fields. Additional methods are optional. But maybe that is a bad idea?
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.
The problem is accessing undefined fields is an error, and they exist in a few Base objects. But we can special case getfields
instead of getoroperties
to check if fields are defined?
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.
You can not have undefined fields in user defined types right? So we could take care of all builtin types that have these?
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.
Yes, I think we could special case in getfields
for undefined fields for Base objects.
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.
There are some objects like that here that currently use getproperties
src/nonstandard.jl
Outdated
@@ -35,6 +35,7 @@ end | |||
function tridiagonal_constructor(dl::V, d::V, du::V, du2::V) where {V<:AbstractVector{T}} where T | |||
Tridiagonal{T,V}(dl, d, du, du2) | |||
end | |||
getfields(o::Tridiagonal) = Tuple(getproperties(o)) |
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.
Shouldn't we swap these? getfields
depending on getproperties
is a bit strange. I'm also not clear why we lose the field names in getfields
?
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.
So you think getfields
should return a NamedTuple
(with the exception of getfields(::Tuple)::Tuple
)? I like that idea, we could then have getproperties(obj) = getfields(obj)
as a default implementation.
This currently says "The semantics of |
Yes we need that flexibility for undefined fields as well. |
@rafaqz , will |
Yes. |
Awesome, thanks! |
@oschulz I see your case. I find it a tricky design problem. We would like to
Does |
Well, it should be the one that is, by contract, the official "inverse" of |
I view undefined fields as a code smell, but maybe there are good reasons for doing this sometimes? We need to support it in Base, because Base is important but otherwise I don't think we should make an effort to support this case? |
@piever, can I pull you in here? I think this might be interesting for StructArrays.jl. |
Yes, but I think there are situations where you would like to pass all the private details to constructorof and then there are cases where you don't want to calculate the computed fields yourself. |
@piever, maybe I should elaborate: If I'm not mistaken, |
I like it - if |
I would like to merge this. If we want bigger API changes like the ones suggested by @oschulz that can be a separate PR. |
@jw3126 I'm curious of the motivation to change Feels like constructing an object from its "semantic" properties is more widely useful, compared to "raw" internal fields. With this change, there would be no way to create an object of a given type from its semantic properties: What are common types where this fields/properties difference matters? What motivates this |
Actually, the ConstructionBase.jl/src/ConstructionBase.jl Lines 180 to 185 in 2b1222f
|
Thanks @aplavin these are very good questions + observations.
It was related to getproperies mostly because we did not have getfields and tried to dodge the issue. This was very awkward,
Hard to judge what is more common. Fortunately in most cases there is no difference. Examples where you want raw fields are something like Flatten or convert a nested struct to GPU.
I don't know what is more common. However right now one can neither rely on constructorof working correctly with fields nor with properties I would say. Do you have use cases for constructing from properties without existing object? If there are sufficient use cases for this, we can think about adding it.
Personally I think overloading
This is just the fallback definition. How else would you implement a fallback |
Thanks for the detailed response! I agree that it was quite inconsistent before, what the relation between fields/properties/constructorof is.
I've not used either, could you please clarify why properties don't work in this case? |
I would argue these cases are exactly where you'd want to use "semantic" fields (with a fallback to raw fields) because it permits additional flexibility on the library author's end. e.g. if I have a type struct MyModel
non_trainable_coefficients::Vector{Int}
# other fields ...
end I would not want |
For trainable parameters I think the right abstraction is an optic. This way you have fine grained runtime control on which params to optimize. You should neither getproperties not getfields let dictate what to optimize. But one of these can be used as a fallback. In contrast to params for move to GPU you probably want to move always the same substructure. Again this should be customizable and neither be dictated by getproperties or getfields. One of these should be used as a default and here I think in most cases you want to move every Array to GPU so I would choose getfields.
I think for flatten you want just to "reshape" information so getfields is the right thing as it maps 1:1 with the struct contents. But I don't do much flattening @rafaqz is the expert. |
I agree, we should get this merged and think about API extensions after. |
Yes, I also think that in the end, we'll need to support both cases (like discussed above): raw fields and semantic fields, and (re-)construction from them. |
I missed that this PR doesn't include |
@rafaqz any objections to merging this? |
Sorry I dont have enough bandwidth to object to anything 😅 |
When asking, I was thinking of how to avoid the worst kind of errors - the code executes, but is silently wrong. I think that the only way is to restrict this fallback definition ConstructionBase.jl/src/ConstructionBase.jl Lines 180 to 185 in 2b1222f
fieldnames == propertynames , by calling
. Otherwise, whenever the number of properties and fields is the same, setproperties would "work" but give wrong results.
For example: julia> using StructArrays, ConstructionBase
julia> a = StructArray(x=1:3)
3-element StructArray(::UnitRange{Int64}) with eltype NamedTuple{(:x,), Tuple{Int64}}:
(x = 1,)
(x = 2,)
(x = 3,)
# OK:
julia> getproperties(a)
(x = 1:3,)
# not OK:
julia> setproperties(a, getproperties(a))
0-element StructArray() with eltype Int64 with indices 1:0 |
That is a great idea! |
function setproperties_object(obj, patch) | ||
check_properties_are_fields(obj) |
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.
@aplavin I added a check as you suggested.
fix #53