1
1
using Accessors
2
- using Accessors: ComposedOptic, PropertyLens, IndexLens, DynamicIndexLens
2
+ using Accessors: PropertyLens, IndexLens, DynamicIndexLens
3
3
using JSON: JSON
4
4
5
- const ALLOWED_OPTICS = Union{typeof (identity),PropertyLens,IndexLens,ComposedOptic }
5
+ const ALLOWED_OPTICS = Union{typeof (identity),PropertyLens,IndexLens,ComposedFunction }
6
6
7
7
"""
8
8
VarName{sym}(optic=identity)
@@ -31,10 +31,11 @@ julia> @varname x[:, 1][1+1]
31
31
x[:, 1][2]
32
32
```
33
33
"""
34
- struct VarName{sym,T}
34
+ struct VarName{sym,T<: ALLOWED_OPTICS }
35
35
optic:: T
36
36
37
37
function VarName {sym} (optic= identity) where {sym}
38
+ optic = normalise (optic)
38
39
if ! is_static_optic (typeof (optic))
39
40
throw (
40
41
ArgumentError (
@@ -53,42 +54,68 @@ Return `true` if `l` is one or a composition of `identity`, `PropertyLens`, and
53
54
one or a composition of `DynamicIndexLens`; and undefined otherwise.
54
55
"""
55
56
is_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}
57
58
return is_static_optic (LO) && is_static_optic (LI)
58
59
end
59
60
is_static_optic (:: Type{<:DynamicIndexLens} ) = false
60
61
61
- # A bit of backwards compatibility.
62
- VarName {sym} (indexing:: Tuple ) where {sym} = VarName {sym} (tupleindex2optic (indexing))
63
-
64
62
"""
65
- VarName(vn::VarName, optic)
66
- VarName(vn::VarName, indexing::Tuple)
63
+ normalise(optic)
67
64
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,
69
68
70
69
```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)
73
72
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))
76
75
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
79
81
```
80
- """
81
- VarName (vn:: VarName , optic= identity) = VarName {getsym(vn)} (optic)
82
82
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)
86
88
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)
91
113
end
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
92
119
93
120
"""
94
121
getsym(vn::VarName)
@@ -105,7 +132,7 @@ julia> getsym(@varname(y))
105
132
:y
106
133
```
107
134
"""
108
- getsym (vn :: VarName{sym} ) where {sym} = sym
135
+ getsym (:: VarName{sym} ) where {sym} = sym
109
136
110
137
"""
111
138
getoptic(vn::VarName)
@@ -154,15 +181,8 @@ function Accessors.set(obj, vn::VarName{sym}, value) where {sym}
154
181
end
155
182
156
183
# 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))
166
186
end
167
187
168
188
Base. hash (vn:: VarName , h:: UInt ) = hash ((getsym (vn), getoptic (vn)), h)
@@ -299,17 +319,17 @@ subsumes(::typeof(identity), ::typeof(identity)) = true
299
319
subsumes (:: typeof (identity), :: ALLOWED_OPTICS ) = true
300
320
subsumes (:: ALLOWED_OPTICS , :: typeof (identity)) = false
301
321
302
- function subsumes (t:: ComposedOptic , u:: ComposedOptic )
322
+ function subsumes (t:: ComposedFunction , u:: ComposedFunction )
303
323
return subsumes (t. outer, u. outer) && subsumes (t. inner, u. inner)
304
324
end
305
325
306
326
# If `t` is still a composed lens, then there is no way it can subsume `u` since `u` is a
307
327
# leaf of the "lens-tree".
308
- subsumes (t:: ComposedOptic , u:: PropertyLens ) = false
328
+ subsumes (t:: ComposedFunction , u:: PropertyLens ) = false
309
329
# Here we need to check if `u.inner` (i.e. the next lens to be applied from `u`) is
310
330
# subsumed by `t`, since this would mean that the rest of the composition is also subsumed
311
331
# by `t`.
312
- subsumes (t:: PropertyLens , u:: ComposedOptic ) = subsumes (t, u. inner)
332
+ subsumes (t:: PropertyLens , u:: ComposedFunction ) = subsumes (t, u. inner)
313
333
314
334
# For `PropertyLens` either they have the same `name` and thus they are indeed the same.
315
335
subsumes (t:: PropertyLens{name} , u:: PropertyLens{name} ) where {name} = true
@@ -321,8 +341,8 @@ subsumes(t::PropertyLens, u::PropertyLens) = false
321
341
# FIXME : Does not correctly handle cases such as `subsumes(x, x[:])`
322
342
# (but neither did old implementation).
323
343
function 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}} ,
326
346
)
327
347
return subsumes_indices (t, u)
328
348
end
@@ -415,7 +435,7 @@ The result is compatible with [`subsumes_indices`](@ref) for `Tuple` input.
415
435
"""
416
436
combine_indices (optic:: ALLOWED_OPTICS ) = (), optic
417
437
combine_indices (optic:: IndexLens ) = (optic. indices,), nothing
418
- function combine_indices (optic:: ComposedOptic {<:ALLOWED_OPTICS,<:IndexLens} )
438
+ function combine_indices (optic:: ComposedFunction {<:ALLOWED_OPTICS,<:IndexLens} )
419
439
indices, next = combine_indices (optic. outer)
420
440
return (optic. inner. indices, indices... ), next
421
441
end
@@ -505,9 +525,9 @@ concretize(I::DynamicIndexLens, x) = concretize(IndexLens(I.f(x)), x)
505
525
function concretize (I:: IndexLens , x)
506
526
return IndexLens (reconcretize_index .(I. indices, to_indices (x, I. indices)))
507
527
end
508
- function concretize (I:: ComposedOptic , x)
528
+ function concretize (I:: ComposedFunction , x)
509
529
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))
511
531
end
512
532
513
533
"""
@@ -533,7 +553,7 @@ julia> # The underlying value is concretized, though:
533
553
ConcretizedSlice(Base.OneTo(100))
534
554
```
535
555
"""
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))
537
557
538
558
"""
539
559
@varname(expr, concretize=false)
@@ -872,7 +892,7 @@ function optic_to_dict(::PropertyLens{sym}) where {sym}
872
892
return Dict (" type" => " property" , " field" => String (sym))
873
893
end
874
894
optic_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 )
876
896
return Dict (
877
897
" type" => " composed" ,
878
898
" outer" => optic_to_dict (c. outer),
@@ -1036,32 +1056,34 @@ ERROR: ArgumentError: optic_to_vn: could not convert optic `(@o _[1])` to a VarN
1036
1056
function optic_to_vn (:: Accessors.PropertyLens{sym} ) where {sym}
1037
1057
return VarName {sym} ()
1038
1058
end
1039
- function optic_to_vn (o:: Base.ComposedFunction{Outer,typeof(identity)} ) where {Outer}
1040
- return optic_to_vn (o. outer)
1041
- end
1042
1059
function optic_to_vn (
1043
1060
o:: Base.ComposedFunction{Outer,Accessors.PropertyLens{sym}}
1044
1061
) where {Outer,sym}
1045
1062
return VarName {sym} (o. outer)
1046
1063
end
1064
+ optic_to_vn (o:: Base.ComposedFunction ) = optic_to_vn (normalise (o))
1047
1065
function optic_to_vn (@nospecialize (o))
1048
1066
msg = " optic_to_vn: could not convert optic `$o ` to a VarName"
1049
1067
throw (ArgumentError (msg))
1050
1068
end
1051
1069
1052
1070
unprefix_optic (o, :: typeof (identity)) = o # Base case
1053
1071
function 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)
1054
1078
# 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)
1057
1081
if inner != inner_prefix
1058
1082
msg = " could not remove prefix $(optic_prefix) from optic $(optic) "
1059
1083
throw (ArgumentError (msg))
1060
1084
end
1061
1085
# 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))
1065
1087
end
1066
1088
1067
1089
"""
@@ -1115,16 +1137,6 @@ y[1].x.a
1115
1137
function prefix (vn:: VarName{sym_vn} , prefix:: VarName{sym_prefix} ) where {sym_vn,sym_prefix}
1116
1138
optic_vn = getoptic (vn)
1117
1139
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
1129
1141
return VarName {sym_prefix} (new_optic_vn)
1130
1142
end
0 commit comments