Skip to content

Commit 1a9c174

Browse files
committed
Emphasizing (non-)mutating behaviour by using bang-bang notation
1 parent 0479a8a commit 1a9c174

File tree

3 files changed

+457
-145
lines changed

3 files changed

+457
-145
lines changed

docs/src/index.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ julia> x = rand(4);
2626
# and primal value based on the type and shape of `x`.
2727
julia> result = DiffResults.HessianResult(x)
2828

29-
# Instead of passing an output buffer to `hessian!`, we pass `result`.
30-
# Note that we re-alias to `result` - this is important! See `hessian!`
31-
# docs for why we do this.
29+
# Instead of passing an output buffer to `ForwardDiff.hessian!`, we pass `result`.
30+
# Note that we re-alias to `result`:
31+
# This is not required in this example since `ForwardDiff.hessian!` mutates `result`;
32+
# however, in general it is important since immutable `DiffResult` instances
33+
# (e.g. `DiffResult` objects with static arrays) cannot be updated in-place.
3234
julia> result = ForwardDiff.hessian!(result, f, x);
3335

3436
# ...and now we can get all the computed data from `result`
@@ -65,12 +67,12 @@ DiffResults.jacobian
6567
DiffResults.hessian
6668
```
6769

68-
## Mutating a `DiffResult`
70+
## Modifying a `DiffResult`
6971

7072
```@docs
71-
DiffResults.value!
72-
DiffResults.derivative!
73-
DiffResults.gradient!
74-
DiffResults.jacobian!
75-
DiffResults.hessian!
73+
DiffResults.value!!
74+
DiffResults.derivative!!
75+
DiffResults.gradient!!
76+
DiffResults.jacobian!!
77+
DiffResults.hessian!!
7678
```

src/DiffResults.jl

Lines changed: 123 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -145,28 +145,36 @@ Note that this method returns a reference, not a copy.
145145
value(r::DiffResult) = r.value
146146

147147
"""
148-
value!(r::DiffResult, x)
148+
value!!(r::DiffResult, x)
149149
150150
Return `s::DiffResult` with the same data as `r`, except for `value(s) == x`.
151151
152-
This function may or may not mutate `r`. If `r::ImmutableDiffResult`, a totally new
153-
instance will be created and returned, whereas if `r::MutableDiffResult`, then `r` will be
154-
mutated in-place and returned. Thus, this function should be called as `r = value!(r, x)`.
152+
!!! warn
153+
This function may or may not mutate `r`.
154+
If `r::ImmutableDiffResult`, a totally new instance will be created and returned,
155+
whereas if `r::MutableDiffResult`, then `r` will be mutated in-place and returned.
156+
Thus, this function should be called as `r = value!!(r, x)`.
155157
"""
156-
value!(r::MutableDiffResult, x::Number) = (r.value = x; return r)
157-
value!(r::MutableDiffResult, x::AbstractArray) = (copyto!(value(r), x); return r)
158-
value!(r::ImmutableDiffResult{O,V}, x::Union{Number,AbstractArray}) where {O,V} = ImmutableDiffResult(convert(V, x), r.derivs)
158+
value!!(r::MutableDiffResult, x::Number) = (r.value = x; return r)
159+
value!!(r::MutableDiffResult, x::AbstractArray) = (copyto!(value(r), x); return r)
160+
value!!(r::ImmutableDiffResult{O,V}, x::Union{Number,AbstractArray}) where {O,V} = ImmutableDiffResult(convert(V, x), r.derivs)
159161

160162
"""
161-
value!(f, r::DiffResult, x)
163+
value!!(f, r::DiffResult, x)
162164
163-
Equivalent to `value!(r::DiffResult, map(f, x))`, but without the implied temporary
165+
Equivalent to `value!!(r::DiffResult, map(f, x))`, but without the implied temporary
164166
allocation (when possible).
167+
168+
!!! warn
169+
This function may or may not mutate `r`.
170+
If `r::ImmutableDiffResult`, a totally new instance will be created and returned,
171+
whereas if `r::MutableDiffResult`, then `r` will be mutated in-place and returned.
172+
Thus, this function should be called as `r = value!!(f, r, x)`.
165173
"""
166-
value!(f, r::MutableDiffResult, x::Number) = (r.value = f(x); return r)
167-
value!(f, r::MutableDiffResult, x::AbstractArray) = (map!(f, value(r), x); return r)
168-
value!(f, r::ImmutableDiffResult{O,V}, x::Number) where {O,V} = value!(r, convert(V, f(x)))
169-
value!(f, r::ImmutableDiffResult{O,V}, x::AbstractArray) where {O,V} = value!(r, convert(V, map(f, x)))
174+
value!!(f, r::MutableDiffResult, x::Number) = (r.value = f(x); return r)
175+
value!!(f, r::MutableDiffResult, x::AbstractArray) = (map!(f, value(r), x); return r)
176+
value!!(f, r::ImmutableDiffResult{O,V}, x::Number) where {O,V} = value!!(r, convert(V, f(x)))
177+
value!!(f, r::ImmutableDiffResult{O,V}, x::AbstractArray) where {O,V} = value!!(r, convert(V, map(f, x)))
170178

171179
# derivative/derivative! #
172180
#------------------------#
@@ -181,61 +189,68 @@ Note that this method returns a reference, not a copy.
181189
derivative(r::DiffResult, ::Type{Val{i}} = Val{1}) where {i} = r.derivs[i]
182190

183191
"""
184-
derivative!(r::DiffResult, x, ::Type{Val{i}} = Val{1})
192+
derivative!!(r::DiffResult, x, ::Type{Val{i}} = Val{1})
185193
186194
Return `s::DiffResult` with the same data as `r`, except `derivative(s, Val{i}) == x`.
187195
188-
This function may or may not mutate `r`. If `r::ImmutableDiffResult`, a totally new
189-
instance will be created and returned, whereas if `r::MutableDiffResult`, then `r` will be
190-
mutated in-place and returned. Thus, this function should be called as
191-
`r = derivative!(r, x, Val{i})`.
196+
!!! warn
197+
This function may or may not mutate `r`.
198+
If `r::ImmutableDiffResult`, a totally new instance will be created and returned,
199+
whereas if `r::MutableDiffResult`, then `r` will be mutated in-place and returned.
200+
Thus, this function should be called as `r = derivative!!(r, x, Val{i})`.
192201
"""
193-
function derivative!(r::MutableDiffResult, x::Number, ::Type{Val{i}} = Val{1}) where {i}
202+
function derivative!!(r::MutableDiffResult, x::Number, ::Type{Val{i}} = Val{1}) where {i}
194203
r.derivs = tuple_setindex(r.derivs, x, Val{i})
195204
return r
196205
end
197206

198-
function derivative!(r::MutableDiffResult, x::AbstractArray, ::Type{Val{i}} = Val{1}) where {i}
207+
function derivative!!(r::MutableDiffResult, x::AbstractArray, ::Type{Val{i}} = Val{1}) where {i}
199208
copyto!(derivative(r, Val{i}), x)
200209
return r
201210
end
202211

203-
function derivative!(r::ImmutableDiffResult, x::Union{Number,StaticArray}, ::Type{Val{i}} = Val{1}) where {i}
212+
function derivative!!(r::ImmutableDiffResult, x::Union{Number,StaticArray}, ::Type{Val{i}} = Val{1}) where {i}
204213
return ImmutableDiffResult(value(r), tuple_setindex(r.derivs, x, Val{i}))
205214
end
206215

207-
function derivative!(r::ImmutableDiffResult, x::AbstractArray, ::Type{Val{i}} = Val{1}) where {i}
216+
function derivative!!(r::ImmutableDiffResult, x::AbstractArray, ::Type{Val{i}} = Val{1}) where {i}
208217
T = tuple_eltype(r.derivs, Val{i})
209218
return ImmutableDiffResult(value(r), tuple_setindex(r.derivs, T(x), Val{i}))
210219
end
211220

212221
"""
213-
derivative!(f, r::DiffResult, x, ::Type{Val{i}} = Val{1})
222+
derivative!!(f, r::DiffResult, x, ::Type{Val{i}} = Val{1})
214223
215-
Equivalent to `derivative!(r::DiffResult, map(f, x), Val{i})`, but without the implied
224+
Equivalent to `derivative!!(r::DiffResult, map(f, x), Val{i})`, but without the implied
216225
temporary allocation (when possible).
226+
227+
!!! warn
228+
This function may or may not mutate `r`.
229+
If `r::ImmutableDiffResult`, a totally new instance will be created and returned,
230+
whereas if `r::MutableDiffResult`, then `r` will be mutated in-place and returned.
231+
Thus, this function should be called as `r = derivative!!(f, r, x, Val{i})`.
217232
"""
218-
function derivative!(f, r::MutableDiffResult, x::Number, ::Type{Val{i}} = Val{1}) where {i}
233+
function derivative!!(f, r::MutableDiffResult, x::Number, ::Type{Val{i}} = Val{1}) where {i}
219234
r.derivs = tuple_setindex(r.derivs, f(x), Val{i})
220235
return r
221236
end
222237

223-
function derivative!(f, r::MutableDiffResult, x::AbstractArray, ::Type{Val{i}} = Val{1}) where {i}
238+
function derivative!!(f, r::MutableDiffResult, x::AbstractArray, ::Type{Val{i}} = Val{1}) where {i}
224239
map!(f, derivative(r, Val{i}), x)
225240
return r
226241
end
227242

228-
function derivative!(f, r::ImmutableDiffResult, x::Number, ::Type{Val{i}} = Val{1}) where {i}
229-
return derivative!(r, f(x), Val{i})
243+
function derivative!!(f, r::ImmutableDiffResult, x::Number, ::Type{Val{i}} = Val{1}) where {i}
244+
return derivative!!(r, f(x), Val{i})
230245
end
231246

232-
function derivative!(f, r::ImmutableDiffResult, x::StaticArray, ::Type{Val{i}} = Val{1}) where {i}
233-
return derivative!(r, map(f, x), Val{i})
247+
function derivative!!(f, r::ImmutableDiffResult, x::StaticArray, ::Type{Val{i}} = Val{1}) where {i}
248+
return derivative!!(r, map(f, x), Val{i})
234249
end
235250

236-
function derivative!(f, r::ImmutableDiffResult, x::AbstractArray, ::Type{Val{i}} = Val{1}) where {i}
251+
function derivative!!(f, r::ImmutableDiffResult, x::AbstractArray, ::Type{Val{i}} = Val{1}) where {i}
237252
T = tuple_eltype(r.derivs, Val{i})
238-
return derivative!(r, map(f, T(x)), Val{i})
253+
return derivative!!(r, map(f, T(x)), Val{i})
239254
end
240255

241256
# special-cased methods #
@@ -251,23 +266,35 @@ Equivalent to `derivative(r, Val{1})`.
251266
gradient(r::DiffResult) = derivative(r)
252267

253268
"""
254-
gradient!(r::DiffResult, x)
269+
gradient!!(r::DiffResult, x)
255270
256271
Return `s::DiffResult` with the same data as `r`, except `gradient(s) == x`.
257272
258-
Equivalent to `derivative!(r, x, Val{1})`; see `derivative!` docs for aliasing behavior.
273+
Equivalent to `derivative!!(r, x, Val{1})`.
274+
275+
!!! warn
276+
This function may or may not mutate `r`.
277+
If `r::ImmutableDiffResult`, a totally new instance will be created and returned,
278+
whereas if `r::MutableDiffResult`, then `r` will be mutated in-place and returned.
279+
Thus, this function should be called as `r = gradient!!(r, x)`.
259280
"""
260-
gradient!(r::DiffResult, x) = derivative!(r, x)
281+
gradient!!(r::DiffResult, x) = derivative!!(r, x)
261282

262283
"""
263-
gradient!(f, r::DiffResult, x)
284+
gradient!!(f, r::DiffResult, x)
264285
265-
Equivalent to `gradient!(r::DiffResult, map(f, x))`, but without the implied temporary
286+
Equivalent to `gradient!!(r::DiffResult, map(f, x))`, but without the implied temporary
266287
allocation (when possible).
267288
268-
Equivalent to `derivative!(f, r, x, Val{1})`; see `derivative!` docs for aliasing behavior.
289+
Equivalent to `derivative!!(f, r, x, Val{1})`.
290+
291+
!!! warn
292+
This function may or may not mutate `r`.
293+
If `r::ImmutableDiffResult`, a totally new instance will be created and returned,
294+
whereas if `r::MutableDiffResult`, then `r` will be mutated in-place and returned.
295+
Thus, this function should be called as `r = gradient!!(f, r, x)`.
269296
"""
270-
gradient!(f, r::DiffResult, x) = derivative!(f, r, x)
297+
gradient!!(f, r::DiffResult, x) = derivative!!(f, r, x)
271298

272299
"""
273300
jacobian(r::DiffResult)
@@ -279,23 +306,35 @@ Equivalent to `derivative(r, Val{1})`.
279306
jacobian(r::DiffResult) = derivative(r)
280307

281308
"""
282-
jacobian!(r::DiffResult, x)
309+
jacobian!!(r::DiffResult, x)
283310
284311
Return `s::DiffResult` with the same data as `r`, except `jacobian(s) == x`.
285312
286-
Equivalent to `derivative!(r, x, Val{1})`; see `derivative!` docs for aliasing behavior.
313+
Equivalent to `derivative!!(r, x, Val{1})`.
314+
315+
!!! warn
316+
This function may or may not mutate `r`.
317+
If `r::ImmutableDiffResult`, a totally new instance will be created and returned,
318+
whereas if `r::MutableDiffResult`, then `r` will be mutated in-place and returned.
319+
Thus, this function should be called as `r = jacobian!!(r, x)`.
287320
"""
288-
jacobian!(r::DiffResult, x) = derivative!(r, x)
321+
jacobian!!(r::DiffResult, x) = derivative!!(r, x)
289322

290323
"""
291-
jacobian!(f, r::DiffResult, x)
324+
jacobian!!(f, r::DiffResult, x)
292325
293-
Equivalent to `jacobian!(r::DiffResult, map(f, x))`, but without the implied temporary
326+
Equivalent to `jacobian!!(r::DiffResult, map(f, x))`, but without the implied temporary
294327
allocation (when possible).
295328
296-
Equivalent to `derivative!(f, r, x, Val{1})`; see `derivative!` docs for aliasing behavior.
329+
Equivalent to `derivative!!(f, r, x, Val{1})`.
330+
331+
!!! warn
332+
This function may or may not mutate `r`.
333+
If `r::ImmutableDiffResult`, a totally new instance will be created and returned,
334+
whereas if `r::MutableDiffResult`, then `r` will be mutated in-place and returned.
335+
Thus, this function should be called as `r = jacobian!!(f, r, x)`.
297336
"""
298-
jacobian!(f, r::DiffResult, x) = derivative!(f, r, x)
337+
jacobian!!(f, r::DiffResult, x) = derivative!!(f, r, x)
299338

300339
"""
301340
hessian(r::DiffResult)
@@ -307,23 +346,35 @@ Equivalent to `derivative(r, Val{2})`.
307346
hessian(r::DiffResult) = derivative(r, Val{2})
308347

309348
"""
310-
hessian!(r::DiffResult, x)
349+
hessian!!(r::DiffResult, x)
311350
312351
Return `s::DiffResult` with the same data as `r`, except `hessian(s) == x`.
313352
314-
Equivalent to `derivative!(r, x, Val{2})`; see `derivative!` docs for aliasing behavior.
353+
Equivalent to `derivative!!(r, x, Val{2})`.
354+
355+
!!! warn
356+
This function may or may not mutate `r`.
357+
If `r::ImmutableDiffResult`, a totally new instance will be created and returned,
358+
whereas if `r::MutableDiffResult`, then `r` will be mutated in-place and returned.
359+
Thus, this function should be called as `r = hessian!(r, x)`.
315360
"""
316-
hessian!(r::DiffResult, x) = derivative!(r, x, Val{2})
361+
hessian!!(r::DiffResult, x) = derivative!!(r, x, Val{2})
317362

318363
"""
319-
hessian!(f, r::DiffResult, x)
364+
hessian!!(f, r::DiffResult, x)
320365
321-
Equivalent to `hessian!(r::DiffResult, map(f, x))`, but without the implied temporary
366+
Equivalent to `hessian!!(r::DiffResult, map(f, x))`, but without the implied temporary
322367
allocation (when possible).
323368
324-
Equivalent to `derivative!(f, r, x, Val{2})`; see `derivative!` docs for aliasing behavior.
369+
Equivalent to `derivative!!(f, r, x, Val{2})`.
370+
371+
!!! warn
372+
This function may or may not mutate `r`.
373+
If `r::ImmutableDiffResult`, a totally new instance will be created and returned,
374+
whereas if `r::MutableDiffResult`, then `r` will be mutated in-place and returned.
375+
Thus, this function should be called as `r = hessian!!(f, r, x)`.
325376
"""
326-
hessian!(f, r::DiffResult, x) = derivative!(f, r, x, Val{2})
377+
hessian!!(f, r::DiffResult, x) = derivative!!(f, r, x, Val{2})
327378

328379
###################
329380
# Pretty Printing #
@@ -333,4 +384,23 @@ Base.show(io::IO, r::ImmutableDiffResult) = print(io, "ImmutableDiffResult($(r.v
333384

334385
Base.show(io::IO, r::MutableDiffResult) = print(io, "MutableDiffResult($(r.value), $(r.derivs))")
335386

387+
################
388+
# Deprecations #
389+
################
390+
391+
Base.@deprecate value!(r::DiffResult, x::Union{Number,AbstractArray}) value!!(r, x) false
392+
Base.@deprecate value!(f, r::DiffResult, x::Union{Number,AbstractArray}) value!!(f, r, x) false
393+
394+
Base.@deprecate derivative!(r::DiffResult, x::Union{Number,AbstractArray}, ::Type{Val{i}} = Val{1}) where {i} derivative!!(r, x, Val{i}) false
395+
Base.@deprecate derivative!(f, r::DiffResult, x::Union{Number,AbstractArray}, ::Type{Val{i}} = Val{1}) where {i} derivative!!(f, r, x, Val{i}) false
396+
397+
Base.@deprecate gradient!(r::DiffResult, x) gradient!!(r, x) false
398+
Base.@deprecate gradient!(f, r::DiffResult, x) gradient!!(f, r, x) false
399+
400+
Base.@deprecate jacobian!(r::DiffResult, x) jacobian!!(r, x) false
401+
Base.@deprecate jacobian!(f, r::DiffResult, x) jacobian!!(f, r, x) false
402+
403+
Base.@deprecate hessian!(r::DiffResult, x) hessian!!(r, x) false
404+
Base.@deprecate hessian!(f, r::DiffResult, x) hessian!!(f, r, x) false
405+
336406
end # module

0 commit comments

Comments
 (0)