Skip to content
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

multi-argument modify #130

Open
aplavin opened this issue Jan 23, 2024 · 4 comments
Open

multi-argument modify #130

aplavin opened this issue Jan 23, 2024 · 4 comments

Comments

@aplavin
Copy link
Member

aplavin commented Jan 23, 2024

I knew I saw it somewhere before but forgot, and now found a discussion by @jw3126 in the Setfield repo – jw3126/Setfield.jl#137 :)

I added this feature to AccessorsExtra a few month ago, turns out to be useful sometimes.
Examples:

julia> old = (a=(1,), b=(2, 3))
julia> new = (a=(10,), b=(2, 0.3))
julia> modify(/, old, (@o _[][]), new)
(a = (0.1,), b = (1.0, 10.0))

julia> acc = (a=10, b=[(d=3, e=40)], c="x")
julia> newval = (a=1, b=[(d=30, e=4)], c="y")
julia> acc = modify(max, acc, RecursiveOfType(Number), newval)
(a = 10, b = [(d = 30, e = 40)], c="x")

Implementation is very simple in https://gitlab.com/aplavin/AccessorsExtra.jl/-/blob/master/src/modifymany.jl. Most of the lines are to handle Elements and Properties.

First, I wonder if you have any suggestions on the interface. Is that useful for what you had in mind?
Second, if this is considered generally within the Accessors scope, we can add it here. Wonder if this is widely useful (I think so, but not without promoting these features...), and worry a bit about "feature creep" of such a foundational package. In comparison, AccessorsExtra contains less clear-cut stuff anyways, so I'm much less worried there :)

@jw3126
Copy link
Member

jw3126 commented Feb 11, 2024

Yes, I think we can have this. I don't love the options we have for the signature though:

modify(f, optic, objs...) # clashes with existing modify(f, obj, optic)
modify(f, obj1, optic, objs...) # a bit ugly to have the optic in between
modify(f, objs..., optic) # a bit cumbersome for dispatch

Alternative we could use a new function for this.

@aplavin
Copy link
Member Author

aplavin commented Feb 11, 2024

Note that obj1 and objs are not fully symmetric in such a function, they cannot in general be swapped.
In particular, obj1 should always define the resulting type. For Properties(), it defines what property names are used (others in objs should be ignored). For Recursive, obj1 should define the whole resulting structure. Etc.

So there's probably nothing too wrong with the modify(f, obj1, optic, objs...) signature: it highlights the special role of obj1. Basically, it's regular 1-obj modify(), plus extra objects added at the end.
Another bonus is that it's easy to support 1-arg and multi-arg modify with exact same code in many cases. Like, in https://gitlab.com/aplavin/AccessorsExtra.jl/-/blob/master/src/modifymany.jl#L16-19 we can replace B and b with B... and b..., getting both 1- and multi-arg modify() as one method.

@jw3126
Copy link
Member

jw3126 commented Feb 15, 2024

That is a good point, now I am more happy with the signature modify(f, obj, optic, objs...).

@aplavin
Copy link
Member Author

aplavin commented Mar 30, 2024

After some usage I quite like it, and may make a PR here soon :)

The one inconvenience I noticed is:

julia> obj = (name="A", values=(red=1, blue=2))
julia> sensitivities = (red=2, green=4, blue=3)

# want to modify obj.values according to sensitivities
# seems straightforward?..
julia> modify(/, obj.values, ₚ, sensitivities)
(red = 0.5, blue = 0.6666666666666666)

# ... but if I want the full obj back, I need manually nested modify calls:
julia> @modify(obj.values) do values
       modify(/, values, ₚ, sensitivities)
end
(name = "A", values = (red = 0.5, blue = 0.6666666666666666))

Some kind of "focusing" optic would seem useful here, but I'm curious what you think about this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants