11using Accessors
2- using Accessors: ComposedOptic, PropertyLens, IndexLens, DynamicIndexLens
2+ using Accessors: PropertyLens, IndexLens, DynamicIndexLens
33using JSON: JSON
44
5- const ALLOWED_OPTICS = Union{typeof (identity),PropertyLens,IndexLens,ComposedOptic }
5+ const ALLOWED_OPTICS = Union{typeof (identity),PropertyLens,IndexLens,ComposedFunction }
66
77"""
88 VarName{sym}(optic=identity)
@@ -31,10 +31,11 @@ julia> @varname x[:, 1][1+1]
3131x[:, 1][2]
3232```
3333"""
34- struct VarName{sym,T}
34+ struct VarName{sym,T<: ALLOWED_OPTICS }
3535 optic:: T
3636
3737 function VarName {sym} (optic= identity) where {sym}
38+ optic = normalise (optic)
3839 if ! is_static_optic (typeof (optic))
3940 throw (
4041 ArgumentError (
@@ -53,42 +54,68 @@ Return `true` if `l` is one or a composition of `identity`, `PropertyLens`, and
5354one or a composition of `DynamicIndexLens`; and undefined otherwise.
5455"""
5556is_static_optic (:: Type{<:Union{typeof(identity),PropertyLens,IndexLens}} ) = true
56- function is_static_optic (:: Type{ComposedOptic {LO,LI}} ) where {LO,LI}
57+ function is_static_optic (:: Type{ComposedFunction {LO,LI}} ) where {LO,LI}
5758 return is_static_optic (LO) && is_static_optic (LI)
5859end
5960is_static_optic (:: Type{<:DynamicIndexLens} ) = false
6061
61- # A bit of backwards compatibility.
62- VarName {sym} (indexing:: Tuple ) where {sym} = VarName {sym} (tupleindex2optic (indexing))
63-
6462"""
65- VarName(vn::VarName, optic)
66- VarName(vn::VarName, indexing::Tuple)
63+ normalise(optic)
6764
68- Return a copy of `vn` with a new index `optic`/`indexing`.
65+ Enforce that compositions of optics are always nested in the same way, in that
66+ a ComposedFunction never has a ComposedFunction as its inner lens. Thus, for
67+ example,
6968
7069```jldoctest; setup=:(using Accessors)
71- julia> VarName(@varname(x[1][2:3]), Accessors.IndexLens((2,)) )
72- x[2]
70+ julia> op1 = ((@o _.c) ∘ (@o _.b)) ∘ (@o _.a )
71+ (@o _.a.b.c)
7372
74- julia> VarName(@varname(x[1][2:3]), ((2,), ))
75- x[2]
73+ julia> op2 = (@o _.c) ∘ ((@o _.b) ∘ (@o _.a ))
74+ (@o _.c) ∘ ((@o _.a.b))
7675
77- julia> VarName(@varname(x[1][2:3]))
78- x
76+ julia> op1 == op2
77+ false
78+
79+ julia> AbstractPPL.normalise(op1) == AbstractPPL.normalise(op2) == @o _.a.b.c
80+ true
7981```
80- """
81- VarName (vn:: VarName , optic= identity) = VarName {getsym(vn)} (optic)
8282
83- function VarName (vn:: VarName , indexing:: Tuple )
84- return VarName {getsym(vn)} (tupleindex2optic (indexing))
85- end
83+ This function also removes redundant `identity` optics from ComposedFunctions:
84+
85+ ```jldoctest; setup=:(using Accessors)
86+ julia> op3 = ((@o _.b) ∘ identity) ∘ (@o _.a)
87+ (@o identity(_.a).b)
8688
87- tupleindex2optic (indexing:: Tuple{} ) = identity
88- tupleindex2optic (indexing:: Tuple{<:Tuple} ) = IndexLens (first (indexing)) # TODO : rest?
89- function tupleindex2optic (indexing:: Tuple )
90- return IndexLens (first (indexing)) ∘ tupleindex2optic (indexing[2 : end ])
89+ julia> op4 = (@o _.b) ∘ (identity ∘ (@o _.a))
90+ (@o _.b) ∘ ((@o identity(_.a)))
91+
92+ julia> AbstractPPL.normalise(op3) == AbstractPPL.normalise(op4) == @o _.a.b
93+ true
94+ ```
95+ """
96+ function normalise (o:: ComposedFunction{Outer,<:ComposedFunction} ) where {Outer}
97+ # `o` is currently (outer ∘ (inner_outer ∘ inner_inner)).
98+ # We want to change this to:
99+ # o = (outer ∘ inner_outer) ∘ inner_inner
100+ inner_inner = o. inner. inner
101+ inner_outer = o. inner. outer
102+ # Recursively call normalise because inner_inner could itself be a
103+ # ComposedFunction
104+ return normalise ((o. outer ∘ inner_outer) ∘ inner_inner)
105+ end
106+ function normalise (o:: ComposedFunction{Outer,typeof(identity)} where {Outer})
107+ # strip outer identity
108+ return normalise (o. outer)
109+ end
110+ function normalise (o:: ComposedFunction{typeof(identity),Inner} where {Inner})
111+ # strip inner identity
112+ return normalise (o. inner)
91113end
114+ normalise (o:: ComposedFunction ) = normalise (o. outer) ∘ o. inner
115+ normalise (o:: ALLOWED_OPTICS ) = o
116+ # These two methods are needed to avoid method ambiguity.
117+ normalise (o:: ComposedFunction{typeof(identity),<:ComposedFunction} ) = normalise (o. inner)
118+ normalise (:: ComposedFunction{typeof(identity),typeof(identity)} ) = identity
92119
93120"""
94121 getsym(vn::VarName)
@@ -105,7 +132,7 @@ julia> getsym(@varname(y))
105132:y
106133```
107134"""
108- getsym (vn :: VarName{sym} ) where {sym} = sym
135+ getsym (:: VarName{sym} ) where {sym} = sym
109136
110137"""
111138 getoptic(vn::VarName)
@@ -154,15 +181,8 @@ function Accessors.set(obj, vn::VarName{sym}, value) where {sym}
154181end
155182
156183# Allow compositions with optic.
157- function Base.:∘ (optic:: ALLOWED_OPTICS , vn:: VarName{sym,<:ALLOWED_OPTICS} ) where {sym}
158- vn_optic = getoptic (vn)
159- if vn_optic == identity
160- return VarName {sym} (optic)
161- elseif optic == identity
162- return vn
163- else
164- return VarName {sym} (optic ∘ vn_optic)
165- end
184+ function Base.:∘ (optic:: ALLOWED_OPTICS , vn:: VarName{sym} ) where {sym}
185+ return VarName {sym} (optic ∘ getoptic (vn))
166186end
167187
168188Base. hash (vn:: VarName , h:: UInt ) = hash ((getsym (vn), getoptic (vn)), h)
@@ -299,17 +319,17 @@ subsumes(::typeof(identity), ::typeof(identity)) = true
299319subsumes (:: typeof (identity), :: ALLOWED_OPTICS ) = true
300320subsumes (:: ALLOWED_OPTICS , :: typeof (identity)) = false
301321
302- function subsumes (t:: ComposedOptic , u:: ComposedOptic )
322+ function subsumes (t:: ComposedFunction , u:: ComposedFunction )
303323 return subsumes (t. outer, u. outer) && subsumes (t. inner, u. inner)
304324end
305325
306326# If `t` is still a composed lens, then there is no way it can subsume `u` since `u` is a
307327# leaf of the "lens-tree".
308- subsumes (t:: ComposedOptic , u:: PropertyLens ) = false
328+ subsumes (t:: ComposedFunction , u:: PropertyLens ) = false
309329# Here we need to check if `u.inner` (i.e. the next lens to be applied from `u`) is
310330# subsumed by `t`, since this would mean that the rest of the composition is also subsumed
311331# by `t`.
312- subsumes (t:: PropertyLens , u:: ComposedOptic ) = subsumes (t, u. inner)
332+ subsumes (t:: PropertyLens , u:: ComposedFunction ) = subsumes (t, u. inner)
313333
314334# For `PropertyLens` either they have the same `name` and thus they are indeed the same.
315335subsumes (t:: PropertyLens{name} , u:: PropertyLens{name} ) where {name} = true
@@ -321,8 +341,8 @@ subsumes(t::PropertyLens, u::PropertyLens) = false
321341# FIXME : Does not correctly handle cases such as `subsumes(x, x[:])`
322342# (but neither did old implementation).
323343function subsumes (
324- t:: Union{IndexLens,ComposedOptic {<:ALLOWED_OPTICS,<:IndexLens}} ,
325- u:: Union{IndexLens,ComposedOptic {<:ALLOWED_OPTICS,<:IndexLens}} ,
344+ t:: Union{IndexLens,ComposedFunction {<:ALLOWED_OPTICS,<:IndexLens}} ,
345+ u:: Union{IndexLens,ComposedFunction {<:ALLOWED_OPTICS,<:IndexLens}} ,
326346)
327347 return subsumes_indices (t, u)
328348end
@@ -415,7 +435,7 @@ The result is compatible with [`subsumes_indices`](@ref) for `Tuple` input.
415435"""
416436combine_indices (optic:: ALLOWED_OPTICS ) = (), optic
417437combine_indices (optic:: IndexLens ) = (optic. indices,), nothing
418- function combine_indices (optic:: ComposedOptic {<:ALLOWED_OPTICS,<:IndexLens} )
438+ function combine_indices (optic:: ComposedFunction {<:ALLOWED_OPTICS,<:IndexLens} )
419439 indices, next = combine_indices (optic. outer)
420440 return (optic. inner. indices, indices... ), next
421441end
@@ -505,9 +525,9 @@ concretize(I::DynamicIndexLens, x) = concretize(IndexLens(I.f(x)), x)
505525function concretize (I:: IndexLens , x)
506526 return IndexLens (reconcretize_index .(I. indices, to_indices (x, I. indices)))
507527end
508- function concretize (I:: ComposedOptic , x)
528+ function concretize (I:: ComposedFunction , x)
509529 x_inner = I. inner (x) # TODO : get view here
510- return ComposedOptic (concretize (I. outer, x_inner), concretize (I. inner, x))
530+ return ComposedFunction (concretize (I. outer, x_inner), concretize (I. inner, x))
511531end
512532
513533"""
@@ -533,7 +553,7 @@ julia> # The underlying value is concretized, though:
533553ConcretizedSlice(Base.OneTo(100))
534554```
535555"""
536- concretize (vn:: VarName , x) = VarName (vn, concretize (getoptic (vn), x))
556+ concretize (vn:: VarName{sym} , x) where {sym} = VarName {sym} ( concretize (getoptic (vn), x))
537557
538558"""
539559 @varname(expr, concretize=false)
@@ -872,7 +892,7 @@ function optic_to_dict(::PropertyLens{sym}) where {sym}
872892 return Dict (" type" => " property" , " field" => String (sym))
873893end
874894optic_to_dict (i:: IndexLens ) = Dict (" type" => " index" , " indices" => index_to_dict (i. indices))
875- function optic_to_dict (c:: ComposedOptic )
895+ function optic_to_dict (c:: ComposedFunction )
876896 return Dict (
877897 " type" => " composed" ,
878898 " outer" => optic_to_dict (c. outer),
@@ -1036,32 +1056,34 @@ ERROR: ArgumentError: optic_to_vn: could not convert optic `(@o _[1])` to a VarN
10361056function optic_to_vn (:: Accessors.PropertyLens{sym} ) where {sym}
10371057 return VarName {sym} ()
10381058end
1039- function optic_to_vn (o:: Base.ComposedFunction{Outer,typeof(identity)} ) where {Outer}
1040- return optic_to_vn (o. outer)
1041- end
10421059function optic_to_vn (
10431060 o:: Base.ComposedFunction{Outer,Accessors.PropertyLens{sym}}
10441061) where {Outer,sym}
10451062 return VarName {sym} (o. outer)
10461063end
1064+ optic_to_vn (o:: Base.ComposedFunction ) = optic_to_vn (normalise (o))
10471065function optic_to_vn (@nospecialize (o))
10481066 msg = " optic_to_vn: could not convert optic `$o ` to a VarName"
10491067 throw (ArgumentError (msg))
10501068end
10511069
10521070unprefix_optic (o, :: typeof (identity)) = o # Base case
10531071function unprefix_optic (optic, optic_prefix)
1072+ # Technically `unprefix_optic` only receives optics that were part of
1073+ # VarNames, so the optics should already be normalised (in the inner
1074+ # constructor of the VarName). However I guess it doesn't hurt to do it
1075+ # again to be safe.
1076+ optic = normalise (optic)
1077+ optic_prefix = normalise (optic_prefix)
10541078 # strip one layer of the optic and check for equality
1055- inner = _inner (_strip_identity ( optic) )
1056- inner_prefix = _inner (_strip_identity ( optic_prefix) )
1079+ inner = _inner (optic)
1080+ inner_prefix = _inner (optic_prefix)
10571081 if inner != inner_prefix
10581082 msg = " could not remove prefix $(optic_prefix) from optic $(optic) "
10591083 throw (ArgumentError (msg))
10601084 end
10611085 # recurse
1062- return unprefix_optic (
1063- _outer (_strip_identity (optic)), _outer (_strip_identity (optic_prefix))
1064- )
1086+ return unprefix_optic (_outer (optic), _outer (optic_prefix))
10651087end
10661088
10671089"""
@@ -1115,16 +1137,6 @@ y[1].x.a
11151137function prefix (vn:: VarName{sym_vn} , prefix:: VarName{sym_prefix} ) where {sym_vn,sym_prefix}
11161138 optic_vn = getoptic (vn)
11171139 optic_prefix = getoptic (prefix)
1118- # Special case `identity` to avoid having ComposedFunctions with identity
1119- if optic_vn == identity
1120- new_inner_optic_vn = PropertyLens {sym_vn} ()
1121- else
1122- new_inner_optic_vn = optic_vn ∘ PropertyLens {sym_vn} ()
1123- end
1124- if optic_prefix == identity
1125- new_optic_vn = new_inner_optic_vn
1126- else
1127- new_optic_vn = new_inner_optic_vn ∘ optic_prefix
1128- end
1140+ new_optic_vn = optic_vn ∘ PropertyLens {sym_vn} () ∘ optic_prefix
11291141 return VarName {sym_prefix} (new_optic_vn)
11301142end
0 commit comments