-
Notifications
You must be signed in to change notification settings - Fork 19
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
get/set all values referred by an optic #63
Comments
#23 looks a bit over my head - not even talking about implementation, I don't fully understand the interface and usecases. Lines 18 to 24 in fa0a446
|
Okay, so is your question whether one can implement I think that is not really possible. For instance: julia> using Accessors
julia> obj = [1,2,3]
3-element Vector{Int64}:
1
2
3
julia> optic = Elements()
Elements()
julia> modify(identity, obj, optic)
3-element Vector{Int64}:
1
2
3
julia> set(obj, optic, 1)
3-element Vector{Int64}:
1
1
1 That means you can only ever construct constant vectors using Mathematically there are many flavors of optics and these can have different APIs and laws. Lens is one with lots of structure and laws.
|
The use case is reconstructing arbitrary immutable objects with new/modified field values. https://github.com/rafaqz/ModelParameters.jl is the best example of how powerful this can be. You can also use it to e.g. replace You can set values using this method, by passing in a tuple. But the order of the matched field types in the object tree determines the order objects are replaced in. |
That describes the whole With simple lenses in Accessors, it's pretty clear when to use them and what's the benefit. More complex optics, such as
That sounds like a job for the |
@jw3126 thanks for the detailed explanation! Do you think there's space for functions like Optics have great composability, so that one learns about a few concepts and they (often) seamlessly combine together. Hoping for something similar with "multivalued" optics, and a function like |
If you manage to implement a fast |
Maybe you miss the part about arbitrary objects. Most of Accessors works on objects where you know a field name or some other detail. But sometimes we don't know anything about the structure of the objects or the names of any of fields, and want to make generic changes to it. Maybe its already possible with accessors? But Im not sure how. If you can define a function that takes the Float64 from any bits type object of any nesting depth and makes them all Float32 in the reconstructed object or a flat Tuple - and any other similar transformation - calculated at compile time. That would remove the need for that PR. How youre describing Edit: I may not have been clear but being able to get/set objects from a |
Yes, I understand the usecase "replace all values of this type to this type" now: like the GPU conversion or Float bitness.
Looked once again into that PR, still not sure if I fully understand the interface. What do you mean "composes with other optics"? When would one use I would say a more intuitive approach wouldn't even involve introducing a new type: just define |
I dont realy get what is different with your proposal to that PR or what you dont understand at this point 😆 It has to be able to replace Flatten.jl and power ModelParameters.jl, as I linked earlier. That was my main goal, with some nice By composes I mean you could do something like Maybe you should try it out rather than reinvent that wheel, if you will likely have the same type stability issue. |
Suppose I have an optic like I haven't tried implementing |
I don't know how @getall (x[2].g for x in missings_obj if x isa NamedTuple)
@setall (x[2].g for x in missings_obj if x isa NamedTuple) = 5 This example gets all the You can do this manually without the macros using See: |
A major point of composability is the ability to just pass an optic, and let the inner function do whatever it wants with the values specified by that optic. Currently, the values can always be
|
"Get me every |
Sorry, do you agree with something, or arguing with something from my previous message? |
I mean its not really worth arguing over which appoach is more general or "arguably more specialised", Im not sure why that's relevent. I have never needed the thing that you want. |
Maybe a poor choice of the words on my end. Of course, "Get me every Float64 in this thing" is generic and works with all kinds of objects, if properly implemented. |
Ok hah. Maybe we are talking past each other? Sure, your proposal is generic in relation to other optics. But not in terms of the interaction with other packages, e.g. like Optim.jl. Getting and setting everything as a flat tuple is a nice generic interface for the rest of the ecosystem. It removes all structure and complexity of objects. Thats the reason Query is written like it is rather than using |
Hm, why?.. My
Sure, that's great and can be useful, as we discussed above. See the example with julia> obj = (x=1,y=2,z=(w=3,u=(a=4,b=5),v=6))
(x = 1, y = 2, z = (w = 3, u = (a = 4, b = 5), v = 6))
julia> modify(x -> 100x, obj, Recursive(x -> !(x isa Number), Properties()))
(x = 100, y = 200, z = (w = 300, u = (a = 400, b = 500), v = 600)) Currently, only |
That's quite surprising for me, actually, considering that you work with nested models and their parameters. struct CompA
x
y
...
end
struct CompB
x
y
...
end
struct MyModel
comp_a::CompA
comps_b::Tuple{CompB...}
end
m = MyModel(CompA(...), (CompB(...), CompB(...))) Now, I need to change (manually/with optimizer/with sampler/whatever) some parameters. o = @optic _.comp_a[(:x, :y)]
tup = getall(m, o)
... work with tup ...
new_m = setall(m, o, new_tup) Want Currently, I use tuples of lenses, and "broadcast" their get/set methods to attain such behavior. However, this only works with scalar lenses -- not |
But how does it set all the numbers in an object from a flat tuple? Setting is the hard part, getting is clearly trivial. (Your example is also trivial right? The problem is when you dont know any of the field names or the structure of what youre working with - im writing generic code for other peoples models, not bespoke methods for my own) |
Do you say this from the interface/API perspective, or regarding implementation? The former seems clear: As for implementation, as I understand neither getting nor setting can be done inferrably now. Is that not the case? |
Getting can be inferrable and is in Flatten.jl, and get/set used to compile away in Flatten.jl before compiler changes killed that. Its still some nanoseconds (like ~50 maybe fir something small? I dont remember) in the query PR here and can compile away when you use Revise.jl (a compiler bug). But not without it, and some use cases have a hard requirement that it compiles away. Setting is harder in terms of implementation because you need to pass the state of your iterator around as you walk the tree. The leaves need to know about their position in the tree somehow. Getting doesnt need that, the order is implicit. |
Mhh I think getting is already non trivial IIRC in #23 getall was not inferrable either. |
But the code is much simpler. Its only a few lines in Flatten.jl and it it compiles away. You also dont have the problem in of iterating the set tuple somehow. |
What do you mean by "trivial" here? I'm not aware of any existing function that can extract all values referenced by an optic.
Note that my proposal is strictly more generic than that! With #23 as-is, there is still no way to extract values defined by an optic (right?). Moreover, the implementation can stay almost the same as #23. I think, what's implemented there is effectively |
I dont think that its actually the same as Doesn't that mean it would set from nested tuples too? How is it a superset of To use flat tuple inputs we will still need some A more minor difference is
By trivial I mean in the development of Flatten.jl the getall analog |
Are you talking about the current situation, or my |
Ok right of course it doesnt. I dont actually use Recursive. But as far as I know there's nowhere in Accessors.jl that flattens anything now. But if you can handle distributing a flat tuple through all composed optics and setting the right part of the object with the right values of the tuple that will be great. It might be easiest to not pass around the iterator as in Query and instead just to call getall at every iteration, see how long the tuple it returns is, and pass a tuple that long with setall. One last problem I haven't mentioned is that Query also only reconstructs objects when there were changes to their contents, to avoid trying to construct objects that dont implement |
Sure, there isn't anything like that yet. Did you see a comment of mine above:
So, the implementation of
Interesting, that can also be useful for performance if constructors exist but perform some checks. I may try implementing |
The inference problem is specifically with recursion, so you should be fine. |
Recursion is there with |
Possible using the new 1.8 |
See the linked PR, I tried adding simple |
It won't because the value in the struct is still determined at runtime. I think we need the opposit - a separate path for descend to just check types match rather than calling a function that returns a Bool. Then we can use |
Given the discussion in this thread and following get/setall PRs, I updated the title to be more relevant. Briefly, the current state is that getting/setting all referred values works for all optics with the exception being |
Regarding compiler improvements: JuliaLang/julia#48059 looks relevant. Do you @jw3126 @rafaqz think that change would allow type-stable "get-all-from-Recursive", or something else is needed? |
One issue that I am not sure is addressed by this PR would be |
But see See basic examples here, and how it transparently works with |
Some existing optics, such as
Elements()
,If()
andRecursive()
, can only bemodify
ed - one cannot retrieve the values specified by them. I understand that it's not obvious how to implementoptic(obj)
for them and even what type it should return. However, from time to time I try to reach for this functionality: either to specifically extract the corresponding values, or just to see what valuesmodify
would be called with.Do you think it's possible to design value extraction for those optics in a consistent manner?
The text was updated successfully, but these errors were encountered: