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
+ # nb. ComposedFunction is the same as Accessors.ComposedOptic
6
+ const ALLOWED_OPTICS = Union{typeof (identity),PropertyLens,IndexLens,ComposedFunction}
6
7
7
8
"""
8
9
VarName{sym}(optic=identity)
@@ -31,10 +32,11 @@ julia> @varname x[:, 1][1+1]
31
32
x[:, 1][2]
32
33
```
33
34
"""
34
- struct VarName{sym,T}
35
+ struct VarName{sym,T<: ALLOWED_OPTICS }
35
36
optic:: T
36
37
37
38
function VarName {sym} (optic= identity) where {sym}
39
+ optic = normalise (optic)
38
40
if ! is_static_optic (typeof (optic))
39
41
throw (
40
42
ArgumentError (
@@ -53,42 +55,68 @@ Return `true` if `l` is one or a composition of `identity`, `PropertyLens`, and
53
55
one or a composition of `DynamicIndexLens`; and undefined otherwise.
54
56
"""
55
57
is_static_optic (:: Type{<:Union{typeof(identity),PropertyLens,IndexLens}} ) = true
56
- function is_static_optic (:: Type{ComposedOptic {LO,LI}} ) where {LO,LI}
58
+ function is_static_optic (:: Type{ComposedFunction {LO,LI}} ) where {LO,LI}
57
59
return is_static_optic (LO) && is_static_optic (LI)
58
60
end
59
61
is_static_optic (:: Type{<:DynamicIndexLens} ) = false
60
62
61
- # A bit of backwards compatibility.
62
- VarName {sym} (indexing:: Tuple ) where {sym} = VarName {sym} (tupleindex2optic (indexing))
63
-
64
63
"""
65
- VarName(vn::VarName, optic)
66
- VarName(vn::VarName, indexing::Tuple)
64
+ normalise(optic)
67
65
68
- Return a copy of `vn` with a new index `optic`/`indexing`.
66
+ Enforce that compositions of optics are always nested in the same way, in that
67
+ a ComposedFunction never has a ComposedFunction as its inner lens. Thus, for
68
+ example,
69
69
70
70
```jldoctest; setup=:(using Accessors)
71
- julia> VarName(@varname(x[1][2:3]), Accessors.IndexLens((2,)) )
72
- x[2]
71
+ julia> op1 = ((@o _.c) ∘ (@o _.b)) ∘ (@o _.a )
72
+ (@o _.a.b.c)
73
73
74
- julia> VarName(@varname(x[1][2:3]), ((2,), ))
75
- x[2]
74
+ julia> op2 = (@o _.c) ∘ ((@o _.b) ∘ (@o _.a ))
75
+ (@o _.c) ∘ ((@o _.a.b))
76
76
77
- julia> VarName(@varname(x[1][2:3]))
78
- x
77
+ julia> op1 == op2
78
+ false
79
+
80
+ julia> AbstractPPL.normalise(op1) == AbstractPPL.normalise(op2) == @o _.a.b.c
81
+ true
79
82
```
80
- """
81
- VarName (vn:: VarName , optic= identity) = VarName {getsym(vn)} (optic)
82
83
83
- function VarName (vn:: VarName , indexing:: Tuple )
84
- return VarName {getsym(vn)} (tupleindex2optic (indexing))
85
- end
84
+ This function also removes redundant `identity` optics from ComposedFunctions:
85
+
86
+ ```jldoctest; setup=:(using Accessors)
87
+ julia> op3 = ((@o _.b) ∘ identity) ∘ (@o _.a)
88
+ (@o identity(_.a).b)
86
89
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 ])
90
+ julia> op4 = (@o _.b) ∘ (identity ∘ (@o _.a))
91
+ (@o _.b) ∘ ((@o identity(_.a)))
92
+
93
+ julia> AbstractPPL.normalise(op3) == AbstractPPL.normalise(op4) == @o _.a.b
94
+ true
95
+ ```
96
+ """
97
+ function normalise (o:: ComposedFunction{Outer,<:ComposedFunction} ) where {Outer}
98
+ # `o` is currently (outer ∘ (inner_outer ∘ inner_inner)).
99
+ # We want to change this to:
100
+ # o = (outer ∘ inner_outer) ∘ inner_inner
101
+ inner_inner = o. inner. inner
102
+ inner_outer = o. inner. outer
103
+ # Recursively call normalise because inner_inner could itself be a
104
+ # ComposedFunction
105
+ return normalise ((o. outer ∘ inner_outer) ∘ inner_inner)
106
+ end
107
+ function normalise (o:: ComposedFunction{Outer,typeof(identity)} where {Outer})
108
+ # strip outer identity
109
+ return normalise (o. outer)
110
+ end
111
+ function normalise (o:: ComposedFunction{typeof(identity),Inner} where {Inner})
112
+ # strip inner identity
113
+ return normalise (o. inner)
91
114
end
115
+ normalise (o:: ComposedFunction ) = normalise (o. outer) ∘ o. inner
116
+ normalise (o:: ALLOWED_OPTICS ) = o
117
+ # These two methods are needed to avoid method ambiguity.
118
+ normalise (o:: ComposedFunction{typeof(identity),<:ComposedFunction} ) = normalise (o. inner)
119
+ normalise (:: ComposedFunction{typeof(identity),typeof(identity)} ) = identity
92
120
93
121
"""
94
122
getsym(vn::VarName)
@@ -105,7 +133,7 @@ julia> getsym(@varname(y))
105
133
:y
106
134
```
107
135
"""
108
- getsym (vn :: VarName{sym} ) where {sym} = sym
136
+ getsym (:: VarName{sym} ) where {sym} = sym
109
137
110
138
"""
111
139
getoptic(vn::VarName)
@@ -154,15 +182,8 @@ function Accessors.set(obj, vn::VarName{sym}, value) where {sym}
154
182
end
155
183
156
184
# 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
185
+ function Base.:∘ (optic:: ALLOWED_OPTICS , vn:: VarName{sym} ) where {sym}
186
+ return VarName {sym} (optic ∘ getoptic (vn))
166
187
end
167
188
168
189
Base. hash (vn:: VarName , h:: UInt ) = hash ((getsym (vn), getoptic (vn)), h)
@@ -299,17 +320,17 @@ subsumes(::typeof(identity), ::typeof(identity)) = true
299
320
subsumes (:: typeof (identity), :: ALLOWED_OPTICS ) = true
300
321
subsumes (:: ALLOWED_OPTICS , :: typeof (identity)) = false
301
322
302
- function subsumes (t:: ComposedOptic , u:: ComposedOptic )
323
+ function subsumes (t:: ComposedFunction , u:: ComposedFunction )
303
324
return subsumes (t. outer, u. outer) && subsumes (t. inner, u. inner)
304
325
end
305
326
306
327
# If `t` is still a composed lens, then there is no way it can subsume `u` since `u` is a
307
328
# leaf of the "lens-tree".
308
- subsumes (t:: ComposedOptic , u:: PropertyLens ) = false
329
+ subsumes (t:: ComposedFunction , u:: PropertyLens ) = false
309
330
# Here we need to check if `u.inner` (i.e. the next lens to be applied from `u`) is
310
331
# subsumed by `t`, since this would mean that the rest of the composition is also subsumed
311
332
# by `t`.
312
- subsumes (t:: PropertyLens , u:: ComposedOptic ) = subsumes (t, u. inner)
333
+ subsumes (t:: PropertyLens , u:: ComposedFunction ) = subsumes (t, u. inner)
313
334
314
335
# For `PropertyLens` either they have the same `name` and thus they are indeed the same.
315
336
subsumes (t:: PropertyLens{name} , u:: PropertyLens{name} ) where {name} = true
@@ -321,8 +342,8 @@ subsumes(t::PropertyLens, u::PropertyLens) = false
321
342
# FIXME : Does not correctly handle cases such as `subsumes(x, x[:])`
322
343
# (but neither did old implementation).
323
344
function subsumes (
324
- t:: Union{IndexLens,ComposedOptic {<:ALLOWED_OPTICS,<:IndexLens}} ,
325
- u:: Union{IndexLens,ComposedOptic {<:ALLOWED_OPTICS,<:IndexLens}} ,
345
+ t:: Union{IndexLens,ComposedFunction {<:ALLOWED_OPTICS,<:IndexLens}} ,
346
+ u:: Union{IndexLens,ComposedFunction {<:ALLOWED_OPTICS,<:IndexLens}} ,
326
347
)
327
348
return subsumes_indices (t, u)
328
349
end
@@ -415,7 +436,7 @@ The result is compatible with [`subsumes_indices`](@ref) for `Tuple` input.
415
436
"""
416
437
combine_indices (optic:: ALLOWED_OPTICS ) = (), optic
417
438
combine_indices (optic:: IndexLens ) = (optic. indices,), nothing
418
- function combine_indices (optic:: ComposedOptic {<:ALLOWED_OPTICS,<:IndexLens} )
439
+ function combine_indices (optic:: ComposedFunction {<:ALLOWED_OPTICS,<:IndexLens} )
419
440
indices, next = combine_indices (optic. outer)
420
441
return (optic. inner. indices, indices... ), next
421
442
end
@@ -505,9 +526,9 @@ concretize(I::DynamicIndexLens, x) = concretize(IndexLens(I.f(x)), x)
505
526
function concretize (I:: IndexLens , x)
506
527
return IndexLens (reconcretize_index .(I. indices, to_indices (x, I. indices)))
507
528
end
508
- function concretize (I:: ComposedOptic , x)
529
+ function concretize (I:: ComposedFunction , x)
509
530
x_inner = I. inner (x) # TODO : get view here
510
- return ComposedOptic (concretize (I. outer, x_inner), concretize (I. inner, x))
531
+ return ComposedFunction (concretize (I. outer, x_inner), concretize (I. inner, x))
511
532
end
512
533
513
534
"""
@@ -533,7 +554,7 @@ julia> # The underlying value is concretized, though:
533
554
ConcretizedSlice(Base.OneTo(100))
534
555
```
535
556
"""
536
- concretize (vn:: VarName , x) = VarName (vn, concretize (getoptic (vn), x))
557
+ concretize (vn:: VarName{sym} , x) where {sym} = VarName {sym} ( concretize (getoptic (vn), x))
537
558
538
559
"""
539
560
@varname(expr, concretize=false)
@@ -872,7 +893,7 @@ function optic_to_dict(::PropertyLens{sym}) where {sym}
872
893
return Dict (" type" => " property" , " field" => String (sym))
873
894
end
874
895
optic_to_dict (i:: IndexLens ) = Dict (" type" => " index" , " indices" => index_to_dict (i. indices))
875
- function optic_to_dict (c:: ComposedOptic )
896
+ function optic_to_dict (c:: ComposedFunction )
876
897
return Dict (
877
898
" type" => " composed" ,
878
899
" outer" => optic_to_dict (c. outer),
@@ -1036,32 +1057,34 @@ ERROR: ArgumentError: optic_to_vn: could not convert optic `(@o _[1])` to a VarN
1036
1057
function optic_to_vn (:: Accessors.PropertyLens{sym} ) where {sym}
1037
1058
return VarName {sym} ()
1038
1059
end
1039
- function optic_to_vn (o:: Base.ComposedFunction{Outer,typeof(identity)} ) where {Outer}
1040
- return optic_to_vn (o. outer)
1041
- end
1042
1060
function optic_to_vn (
1043
1061
o:: Base.ComposedFunction{Outer,Accessors.PropertyLens{sym}}
1044
1062
) where {Outer,sym}
1045
1063
return VarName {sym} (o. outer)
1046
1064
end
1065
+ optic_to_vn (o:: Base.ComposedFunction ) = optic_to_vn (normalise (o))
1047
1066
function optic_to_vn (@nospecialize (o))
1048
1067
msg = " optic_to_vn: could not convert optic `$o ` to a VarName"
1049
1068
throw (ArgumentError (msg))
1050
1069
end
1051
1070
1052
1071
unprefix_optic (o, :: typeof (identity)) = o # Base case
1053
1072
function unprefix_optic (optic, optic_prefix)
1073
+ # Technically `unprefix_optic` only receives optics that were part of
1074
+ # VarNames, so the optics should already be normalised (in the inner
1075
+ # constructor of the VarName). However I guess it doesn't hurt to do it
1076
+ # again to be safe.
1077
+ optic = normalise (optic)
1078
+ optic_prefix = normalise (optic_prefix)
1054
1079
# strip one layer of the optic and check for equality
1055
- inner = _inner (_strip_identity ( optic) )
1056
- inner_prefix = _inner (_strip_identity ( optic_prefix) )
1080
+ inner = _inner (optic)
1081
+ inner_prefix = _inner (optic_prefix)
1057
1082
if inner != inner_prefix
1058
1083
msg = " could not remove prefix $(optic_prefix) from optic $(optic) "
1059
1084
throw (ArgumentError (msg))
1060
1085
end
1061
1086
# recurse
1062
- return unprefix_optic (
1063
- _outer (_strip_identity (optic)), _outer (_strip_identity (optic_prefix))
1064
- )
1087
+ return unprefix_optic (_outer (optic), _outer (optic_prefix))
1065
1088
end
1066
1089
1067
1090
"""
@@ -1115,16 +1138,6 @@ y[1].x.a
1115
1138
function prefix (vn:: VarName{sym_vn} , prefix:: VarName{sym_prefix} ) where {sym_vn,sym_prefix}
1116
1139
optic_vn = getoptic (vn)
1117
1140
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
1141
+ new_optic_vn = optic_vn ∘ PropertyLens {sym_vn} () ∘ optic_prefix
1129
1142
return VarName {sym_prefix} (new_optic_vn)
1130
1143
end
0 commit comments