Kaleido.jl is a collection of useful
Lens
es
and helper functions/macros built on top of
Setfield.jl.
- Batched/multi-valued update. See
@batchlens
,MultiLens
. - Get/set multiple and nested fields as a
StaticArray
or any arbitrary multi-valued container. Seegetting
. - Get/set fields with different parametrizations.
See
converting
,setting
,getting
. - Computing other fields during
set
andget
; i.e., adding constraints between fields. Seeconstraining
. - Get/set dynamically computed locations. See
FLens
.
Macro @batchlens
can be used to update various nested locations in a
complex immutable object:
julia> using Setfield, Kaleido
julia> lens_batch = @batchlens begin
_.a.b.c
_.a.b.d[1]
_.a.b.d[3] ∘ settingas𝕀
_.a.e
end;
julia> obj = (a = (b = (c = 1, d = (2, 3, 0.5)), e = 5),);
julia> get(obj, lens_batch)
(1, 2, 0.0, 5)
julia> set(obj, lens_batch, (10, 20, Inf, 50))
(a = (b = (c = 10, d = (20, 3, 1.0)), e = 50),)
(See below for what settingas𝕀
does.)
It is often useful to get the values of the fields as a vector (e.g.,
when optimizing a composite object with Optim.jl). This can be done
with getting(f)
where f
is a constructor.
julia> using StaticArrays
julia> lens_vec = lens_batch ∘ getting(SVector);
julia> @assert get(obj, lens_vec) === SVector(1, 2, 0.0, 5)
julia> set(obj, lens_vec, SVector(10, 20, Inf, 50))
(a = (b = (c = 10.0, d = (20.0, 3, 1.0)), e = 50.0),)
Kaleido.jl comes with lenses settingasℝ₊
, settingasℝ₋
, and
settingas𝕀
to manipulating fields that have to be restricted to be
positive, negative, and in [0, 1]
interval, respectively. Similarly
there are lenses gettingasℝ₊
, gettingasℝ₋
, and gettingas𝕀
to get
values in those domains. The naming is borrowed from
TransformVariables.jl.
julia> lens = (@lens _.x) ∘ settingasℝ₊;
julia> get((x=1.0,), lens) # log(1.0)
0.0
julia> set((x=1.0,), lens, -Inf)
(x = 0.0,)
Kaleido.jl also works with AbstractTransform
defined in
TransformVariables.jl:
julia> using TransformVariables
julia> lens = (@lens _.y[2]) ∘ setting(as𝕀);
julia> obj = (x=0, y=(1, 0.5, 3));
julia> get(obj, lens)
0.0
julia> @assert set(obj, lens, Inf).y[2] ≈ 1
It also is quite easy to define ad-hoc converting accessors using
converting
:
julia> lens = (@lens _.y[2]) ∘
converting(fromfield=x -> parse(Int, x), tofield=string);
julia> obj = (x=0, y=(1, "5", 3));
julia> get(obj, lens)
5
julia> set(obj, lens, 1)
(x = 0, y = (1, "1", 3))
It is easy to add constraints between fields using constraining
.
For example, you can impose that field .c
must be a sum of .a
and
.b
by:
julia> obj = (a = 1, b = 2, c = 3);
julia> constraint = constraining() do obj
@set obj.c = obj.a + obj.b
end;
julia> lens = constraint ∘ MultiLens((
(@lens _.a),
(@lens _.b),
));
julia> get(obj, lens)
(1, 2)
julia> set(obj, lens, (100, 20))
(a = 100, b = 20, c = 120)
Notice that .c
is updated as well in the last line.
You can use FLens
to get
and set
, e.g., the last entry of a
linked list.