I have a heavily nested type.
Somewhere about 7 or 9 layers of composition down there is are some number of
fields that match the should_replace predicate:
And I want to replace each of those with a Foo(20).
Basically these control a hyperparameter that affects the speed accurasy tradeoff in my code,
and for testing i want to speed it right up, so i want to cap its valuel
If I dig down and find them myself i can do it with SetField.
But sometimes the data structure changes, and there might be more of them or less of them.
using ConstructionBase
struct Foo
bar
end
should_replace(::Any) = false
should_replace(x::Foo) = x.bar > 20
function mapproperties(f, obj)
new_props = map(f, getproperties(obj))
setproperties(obj, new_props)
end
function foo20fy(obj)
mapproperties(obj) do val
if should_replace(val)
Foo(20)
else
mapproperties(foo20fy, val)
end
end
end
obj = (
a = Foo(30),
b = (
a = 32,
b = [1,2,3]
),
v = (
a = Foo(19),
b = (Foo(21), 32),
)
)
foo20fy(obj)
(a = Foo(20), b = (a = 32, b = [1, 2, 3]), v = (a = Foo(19), b = (Foo(20), 32)))
It is also possible to do this with Accessors.jl though there might be breaking changes.
This seems very flaky to edge case like if the object contains a Dict or a Tuple.
Which I can work around, but is there something that already handles these as well naturally?
For anyone interested this is how far i got.
I think it works except for the issue above.
function replace(component, predicate, replacement)
gen = Base.Generator(pairs(getproperties(component))) do (fname, fval)
fname => _replace_1(fname, fval, predicate, replacement)
end
return setproperties(component, (; gen...))
end
function replace(component::Tuple, predicate, replacement)
gen = Base.Generator(getproperties(component)) do fval
_replace_1(:_, fval, predicate, replacement)
end
return Tuple(gen)
end
function replace(component::Dict, predicate, replacement)
gen = Base.Generator(component) do (fname, fval)
# don't pass the fname (i.e. the dict key) as it isn't a field name
fname => _replace_1(:_, fval, predicate, replacement)
end
return Dict(gen)
end
function replace(component::Array, predicate, replacement)
return map(component) do fval
_replace_1(:_, fval, predicate, replacement)
end
end
# How to process single elements
function _replace_1(fname, fval, predicate, replacement)
if predicate(fname, fval)
replacement(fname, fval)
else
replace(fval, predicate, replacement) # recursively rewrite that field.
end
end
# Early termination cases to speed things up/avoid errors. Don't recursive
_replace_1(::Any, m::Number, ) = m
_replace_1(::Any, m::Function, ) = m
_replace_1(::Any, m::Type, ) = m
_replace_1(::Any, m::AbstractArray{<:Number}, ) = m