Setfield.jl
Setfield.jl copied to clipboard
Multi argument modify?
From time to time, I do an add hoc implementation of the following functionality:
using Setfield
import Setfield: modify
"""
modify(f, obj, obj1, obj2..., lens)
Multi argument `modify`. Apply `set` to `obj` with the value obtained from `f` on the `get` of all objects.
"""
function modify(f, objects_lens...)
lens = last(objects_lens)
args = let objects = Base.front(objects_lens),
lens=lens
map(objects) do o
get(o, lens)
end
end
val = f(args...)
o1 = first(objects_lens)
return set(o1, lens, val)
end
Usage is as follows:
meas1 = (unit=:cm, values=[1,2])
meas2 = (unit=:cm, values=[3,4])
modify(+, meas1, meas2, @lens(_.values))
(unit = :cm, values = [4, 6])
It has footgun potential though:
meas1 = (unit=:cm, values=[1,2])
meas2 = (unit=:mm, values=[3,4])
modify(+, meas1, meas2, @lens(_.values))
(unit = :cm, values = [4, 6])
Oh, it looks useful. It's a bit like mergewith
? By the way, why not val = foldl(f, args)
instead of val = f(args...)
? (Then it's pretty close to mergewith
.)
Maybe all the field names and values except the one specified by the lens have to match? Throwing when it's not the case seems to be useful to avoid the footgun. Though I guess it's not implementable only by using lens. I guess you can do this by setting the value from the first object to other objects and then make sure everything is equal.
Regarding the method signature, for this function, I think having lens
as the second argument makes sense (even though I argued for lens
to be the last for modify
). It'd be nice to have something like $nice_name((lens1 => f1, lens2 => f2, ...), obj1, obj2, ...)
to do some kind of batched merge.
FYI, I created something similar called modifying
and put it DataTools.jl:
julia> using DataTools julia> map(modifying(a = string), [(a = 1, b = 2), (a = 3, b = 4)]) 2-element Array{NamedTuple{(:a, :b),Tuple{String,Int64}},1}: (a = "1", b = 2) (a = "3", b = 4) julia> reduce(modifying(a = +), [(a = 1, b = 2), (a = 3, b = 4)]) (a = 4, b = 2) julia> using Setfield julia> map(modifying(@lens(_.a[1].b) => x -> 10x), [(a = ((b = 1,), 2),), (a = ((b = 3,), 4),)]) 2-element Array{NamedTuple{(:a,),Tuple{Tuple{NamedTuple{(:b,),Tuple{Int64}},Int64}}},1}: (a = ((b = 10,), 2),) (a = ((b = 30,), 4),)
--- https://juliafolds.github.io/DataTools.jl/dev/#DataTools.modifying