1- export @set , @optic , @reset
1+ export @set , @optic , @reset , @modify
22using MacroTools
33
44"""
@@ -57,11 +57,48 @@ macro reset(ex)
5757 setmacro (identity, ex, overwrite= true )
5858end
5959
60+ """
61+
62+ @modify(f, obj_optic)
63+
64+ Define an optic and call [`modify`](@ref) on it.
65+ ```jldoctest
66+ julia> using Accessors
67+
68+ julia> xs = (1,2,3);
69+
70+ julia> ys = @modify(xs |> Elements() |> If(isodd)) do x
71+ x + 1
72+ end
73+ (2, 2, 4)
74+ ```
75+ Supports the same syntax as [`@optic`](@ref). See also [`@set`](@ref).
76+ """
77+ macro modify (f, obj_optic)
78+ modifymacro (identity, f, obj_optic)
79+ end
80+
81+ """
82+ modifymacro(optictransform, f, obj_optic)
83+
84+ This function can be used to create a customized variant of [`@modify`](@ref).
85+ See also [`opticmacro`](@ref), [`setmacro`](@ref).
86+ """
87+
88+ function modifymacro (optictransform, f, obj_optic)
89+ f = esc (f)
90+ obj, optic = parse_obj_optic (obj_optic)
91+ :(let
92+ optic = $ (optictransform)($ optic)
93+ ($ modify)($ f, $ obj, optic)
94+ end )
95+ end
96+
6097foldtree (op, init, x) = op (init, x)
6198foldtree (op, init, ex:: Expr ) =
6299 op (foldl ((acc, x) -> foldtree (op, acc, x), ex. args; init= init), ex)
63100
64- need_dynamic_lens (ex) =
101+ need_dynamic_optic (ex) =
65102 foldtree (false , ex) do yes, x
66103 yes || x === :end || x === :_
67104 end
@@ -81,65 +118,65 @@ function lower_index(collection::Symbol, index, dim)
81118 return index
82119end
83120
84- function parse_obj_lenses (ex)
121+ function parse_obj_optics (ex)
85122 if @capture (ex, (front_ |> back_))
86- obj, frontlens = parse_obj_lenses (front)
87- backlens = try
123+ obj, frontoptic = parse_obj_optics (front)
124+ backoptic = try
88125 # allow e.g. obj |> first |> _.a.b
89- obj_back, backlens = parse_obj_lenses (back)
126+ obj_back, backoptic = parse_obj_optics (back)
90127 if obj_back == esc (:_ )
91- backlens
128+ backoptic
92129 else
93130 (esc (back),)
94131 end
95132 catch ArgumentError
96- backlens = (esc (back),)
133+ backoptic = (esc (back),)
97134 end
98- return obj, tuple (frontlens ... , backlens ... )
135+ return obj, tuple (frontoptic ... , backoptic ... )
99136 elseif @capture (ex, front_[indices__])
100- obj, frontlens = parse_obj_lenses (front)
101- if any (need_dynamic_lens , indices)
137+ obj, frontoptic = parse_obj_optics (front)
138+ if any (need_dynamic_optic , indices)
102139 @gensym collection
103140 indices = replace_underscore .(indices, collection)
104141 dims = length (indices) == 1 ? nothing : 1 : length (indices)
105142 lindices = esc .(lower_index .(collection, indices, dims))
106- lens = :($ DynamicIndexLens ($ (esc (collection)) -> ($ (lindices... ),)))
143+ optic = :($ DynamicIndexLens ($ (esc (collection)) -> ($ (lindices... ),)))
107144 else
108145 index = esc (Expr (:tuple , indices... ))
109- lens = :($ IndexLens ($ index))
146+ optic = :($ IndexLens ($ index))
110147 end
111148 elseif @capture (ex, front_. property_)
112149 property isa Union{Symbol,String} || throw (ArgumentError (
113150 string (" Error while parsing :($ex ). Second argument to `getproperty` can only be" ,
114151 " a `Symbol` or `String` literal, received `$property ` instead." )
115152 ))
116- obj, frontlens = parse_obj_lenses (front)
117- lens = :($ PropertyLens {$(QuoteNode(property))} ())
153+ obj, frontoptic = parse_obj_optics (front)
154+ optic = :($ PropertyLens {$(QuoteNode(property))} ())
118155 elseif @capture (ex, f_ (front_))
119- obj, frontlens = parse_obj_lenses (front)
120- lens = esc (f) # function lens
156+ obj, frontoptic = parse_obj_optics (front)
157+ optic = esc (f) # function optic
121158 else
122159 obj = esc (ex)
123160 return obj, ()
124161 end
125- return (obj, tuple (frontlens ... , lens ))
162+ return (obj, tuple (frontoptic ... , optic ))
126163end
127164
128165"""
129- opticcompose([lens ₁, [lens ₂, [lens ₃, ...]]])
166+ opticcompose([optic ₁, [optic ₂, [optic ₃, ...]]])
130167
131- Compose `lens ₁`, `lens ₂` etc. There is one subtle point here:
132- While the two composition orders `(lens ₁ ⨟ lens ₂) ⨟ lens ₃` and `lens ₁ ⨟ (lens ₂ ⨟ lens ₃)` have equivalent semantics, their performance may not be the same.
168+ Compose `optic ₁`, `optic ₂` etc. There is one subtle point here:
169+ While the two composition orders `(optic ₁ ⨟ optic ₂) ⨟ optic ₃` and `optic ₁ ⨟ (optic ₂ ⨟ optic ₃)` have equivalent semantics, their performance may not be the same.
133170
134171The `opticcompose` function tries to use a composition order, that the compiler likes. The composition order is therefore not part of the stable API.
135172"""
136173opticcompose () = identity
137174opticcompose (args... ) = opcompose (args... )
138175
139- function parse_obj_lens (ex)
140- obj, lenses = parse_obj_lenses (ex)
141- lens = Expr (:call , opticcompose, lenses ... )
142- obj, lens
176+ function parse_obj_optic (ex)
177+ obj, optics = parse_obj_optics (ex)
178+ optic = Expr (:call , opticcompose, optics ... )
179+ obj, optic
143180end
144181
145182function get_update_op (sym:: Symbol )
@@ -163,10 +200,12 @@ end
163200 setmacro(optictransform, ex::Expr; overwrite::Bool=false)
164201
165202This function can be used to create a customized variant of [`@set`](@ref).
166- It works by applying `optictransform` to the lens that is used in the customized `@set` macro
203+ It works by applying `optictransform` to the optic that is used in the customized `@set` macro
167204at runtime.
205+
206+ # Example
168207```julia
169- function mytransform(lens ::Lens)::Lens
208+ function mytransform(optic ::Lens)::Lens
170209 ...
171210end
172211macro myset(ex)
@@ -179,20 +218,20 @@ function setmacro(optictransform, ex::Expr; overwrite::Bool=false)
179218 @assert ex. head isa Symbol
180219 @assert length (ex. args) == 2
181220 ref, val = ex. args
182- obj, lens = parse_obj_lens (ref)
221+ obj, optic = parse_obj_optic (ref)
183222 dst = overwrite ? obj : gensym (" _" )
184223 val = esc (val)
185224 ret = if ex. head == :(= )
186225 quote
187- lens = ($ optictransform)($ lens )
188- $ dst = $ set ($ obj, lens , $ val)
226+ optic = ($ optictransform)($ optic )
227+ $ dst = $ set ($ obj, optic , $ val)
189228 end
190229 else
191230 op = get_update_op (ex. head)
192231 f = :($ _UpdateOp ($ op,$ val))
193232 quote
194- lens = ($ optictransform)($ lens )
195- $ dst = $ modify ($ f, $ obj, lens )
233+ optic = ($ optictransform)($ optic )
234+ $ dst = $ modify ($ f, $ obj, optic )
196235 end
197236 end
198237 ret
201240"""
202241 @optic
203242
204- Construct a lens from a field access.
243+ Construct an optic from property access and similar .
205244
206245# Example
207246
@@ -240,38 +279,38 @@ end
240279 opticmacro(optictransform, ex::Expr)
241280
242281This function can be used to create a customized variant of [`@optic`](@ref).
243- It works by applying `optictransform` to the created lens at runtime.
282+ It works by applying `optictransform` to the created optic at runtime.
244283```julia
245- # new_lens = mytransform(lens )
246- macro mylens (ex)
284+ # new_optic = mytransform(optic )
285+ macro myoptic (ex)
247286 opticmacro(mytransform, ex)
248287end
249288```
250289See also [`setmacro`](@ref).
251290"""
252291function opticmacro (optictransform, ex)
253- obj, lens = parse_obj_lens (ex)
292+ obj, optic = parse_obj_optic (ex)
254293 if obj != esc (:_ )
255- msg = """ Cannot parse lens $ex . Lens expressions must start with _, got $obj instead."""
294+ msg = """ Cannot parse optic $ex . Lens expressions must start with _, got $obj instead."""
256295 throw (ArgumentError (msg))
257296 end
258- :($ (optictransform)($ lens ))
297+ :($ (optictransform)($ optic ))
259298end
260299
261300
262- _show (io:: IO , lens :: PropertyLens{field} ) where {field} = print (io, " (@optic _.$field )" )
263- _show (io:: IO , lens :: IndexLens ) = print (io, " (@optic _[" , join (repr .(lens . indices), " , " ), " ])" )
264- Base. show (io:: IO , lens :: Union{IndexLens, PropertyLens} ) = _show (io, lens )
265- Base. show (io:: IO , :: MIME"text/plain" , lens :: Union{IndexLens, PropertyLens} ) = _show (io, lens )
301+ _show (io:: IO , optic :: PropertyLens{field} ) where {field} = print (io, " (@optic _.$field )" )
302+ _show (io:: IO , optic :: IndexLens ) = print (io, " (@optic _[" , join (repr .(optic . indices), " , " ), " ])" )
303+ Base. show (io:: IO , optic :: Union{IndexLens, PropertyLens} ) = _show (io, optic )
304+ Base. show (io:: IO , :: MIME"text/plain" , optic :: Union{IndexLens, PropertyLens} ) = _show (io, optic )
266305
267306# debugging
268- show_composition_order (lens ) = (show_composition_order (stdout , lens ); println ())
269- show_composition_order (io:: IO , lens ) = show (io, lens )
270- function show_composition_order (io:: IO , lens :: ComposedOptic )
307+ show_composition_order (optic ) = (show_composition_order (stdout , optic ); println ())
308+ show_composition_order (io:: IO , optic ) = show (io, optic )
309+ function show_composition_order (io:: IO , optic :: ComposedOptic )
271310 print (io, " (" )
272- show_composition_order (io, lens . outer)
311+ show_composition_order (io, optic . outer)
273312 print (io, " ∘ " )
274- show_composition_order (io, lens . inner)
313+ show_composition_order (io, optic . inner)
275314 print (io, " )" )
276315end
277316
0 commit comments