BangBang.jl
BangBang.BangBang
BangBang.Extras
BangBang.NoBang.Empty
BangBang.Extras.modify!!
BangBang.NoBang.singletonof
BangBang.SetfieldImpl.prefermutation
BangBang.add!!
BangBang.append!!
BangBang.broadcast!!
BangBang.collector
BangBang.delete!!
BangBang.deleteat!!
BangBang.empty!!
BangBang.finish!
BangBang.lmul!!
BangBang.materialize!!
BangBang.merge!!
BangBang.mergewith!!
BangBang.mul!!
BangBang.pop!!
BangBang.popfirst!!
BangBang.push!!
BangBang.pushfirst!!
BangBang.rmul!!
BangBang.setdiff!!
BangBang.setindex!!
BangBang.setproperties!!
BangBang.setproperty!!
BangBang.splice!!
BangBang.union!!
BangBang.unique!!
BangBang.@!
BangBang.SetfieldImpl.@set!!
BangBang.BangBang
— ModuleBangBang
BangBang.jl implements functions whose name ends with !!
. Those functions provide a uniform interface for mutable and immutable data structures. Furthermore, those functions implement the "widening" fallback for the case when the usual mutating function does not work (e.g., push!!(Int[], 1.5)
creates a new array Float64[1.5]
).
See the supported functions in the documentation
BangBang.add!!
— Functionadd!!(A, B) -> A′
A .+= B
if possible; otherwise return A .+ B
.
Examples
julia> using BangBang: add!!
julia> add!!((1,), (2,))
(3,)
julia> add!!([1], [2])
1-element Vector{Int64}:
3
BangBang.append!!
— Functionappend!!(dest, src) -> dest′
Append items in src
to dest
. Mutate dest
if possible.
This function "owns" dest
but not src
; i.e., returned value dest′
does not alias src
. For example, append!!(Empty(Vector), src)
shallow-copies src
instead of returning src
as-is.
See also push!!
.
Examples
julia> using BangBang
julia> append!!((1, 2), (3, 4))
(1, 2, 3, 4)
julia> append!!([1, 2], (3, 4))
4-element Vector{Int64}:
1
2
3
4
julia> using StaticArrays: SVector
julia> @assert append!!(SVector(1, 2), (3, 4)) === SVector(1, 2, 3, 4)
julia> using DataFrames: DataFrame
julia> @assert append!!(DataFrame(a=[1], b=[2]), [(a=3.0, b=4.0)]) ==
DataFrame(a=[1.0, 3.0], b=[2.0, 4.0])
julia> using StructArrays: StructVector
julia> @assert append!!(StructVector(a=[1], b=[2]), [(a=3.5, b=4.5)]) ==
StructVector(a=[1.0, 3.5], b=[2.0, 4.5])
julia> using TypedTables: Table
julia> @assert append!!(Table(a=[1], b=[2]), [(a=3.5, b=4.5)]) ==
Table(a=[1.0, 3.5], b=[2.0, 4.5])
append!!
does not own the second argument:
julia> xs = [1, 2, 3];
julia> ys = append!!(Empty(Vector), xs)
3-element Vector{Int64}:
1
2
3
julia> ys === xs
false
BangBang.broadcast!!
— Functionbroadcast!!(f, dest, As...) -> dest′
A mutate-or-widen version of dest .= f.(As...)
.
BangBang.collector
— Functioncollector(data::AbstractVector, unsafe::Val = Val(false)) -> c::AbstractCollector
collector(ElType::Type = Union{}) -> c::AbstractCollector
Create a "collector" c
that can be used to collect elements; i.e., it supports append!!
and push!!
. Appending and pushing elements to a collector are more efficient than doing these operations directly to a vector.
Use finish!(c)
to get the collected data as a vector.
push!!
on the collector can be further optimized by passing Val(true)
to the second unsafe
argument. This is valid to use only if the number of elements appended to c
is less than or equal to length(data)
.
Examples
julia> using BangBang
julia> c = collector()
c = push!!(c, 1)
c = push!!(c, 0.5)
finish!(c)
2-element Vector{Float64}:
1.0
0.5
julia> finish!(append!!(collector(), (x for x in Any[1, 2.0, 3im])))
3-element Vector{ComplexF64}:
1.0 + 0.0im
2.0 + 0.0im
0.0 + 3.0im
julia> finish!(append!!(collector(Vector{Float64}(undef, 10), Val(true)), [1, 2, 3]))
3-element Vector{Float64}:
1.0
2.0
3.0
BangBang.delete!!
— Functiondelete!!(assoc, key) -> assoc′
Examples
julia> using BangBang
julia> delete!!((a=1, b=2), :a)
(b = 2,)
julia> delete!!(Dict(:a=>1, :b=>2), :a)
Dict{Symbol, Int64} with 1 entry:
:b => 2
BangBang.deleteat!!
— Functiondeleteat!!(assoc, i) -> assoc′
Examples
julia> using BangBang
julia> deleteat!!((1, 2, 3), 2)
(1, 3)
julia> deleteat!!([1, 2, 3], 2)
2-element Vector{Int64}:
1
3
julia> using StaticArrays: SVector
julia> @assert deleteat!!(SVector(1, 2, 3), 2) === SVector(1, 3)
BangBang.empty!!
— Functionempty!!(collection) -> collection′
Examples
julia> using BangBang
julia> empty!!((1, 2, 3))
()
julia> empty!!((a=1, b=2, c=3))
NamedTuple()
julia> xs = [1, 2, 3];
julia> empty!!(xs)
Int64[]
julia> xs
Int64[]
julia> using StaticArrays: SVector
julia> @assert empty!!(SVector(1, 2)) == SVector{0, Int}()
BangBang.finish!
— Functionfinish!(c::AbstractCollector) -> data::AbstractVector
Extract the data
collected in the collector c
.
See collector
.
BangBang.lmul!!
— Functionlmul!!(A, B) -> B′
BangBang.materialize!!
— Methodmaterialize!!(dest, x)
julia> using BangBang
julia> using Base.Broadcast: instantiate, broadcasted
julia> bc = instantiate(broadcasted(+, [1.0, 1.5, 2.0], 1));
julia> xs = zeros(Float64, 3);
julia> ys = materialize!!(xs, bc)
3-element Vector{Float64}:
2.0
2.5
3.0
julia> xs === ys # mutated
true
julia> xs = Vector{Union{}}(undef, 3);
julia> ys = materialize!!(xs, bc)
3-element Vector{Float64}:
2.0
2.5
3.0
julia> xs === ys
false
BangBang.merge!!
— Functionmerge!!(dictlike, others...) -> dictlike′
merge!!(combine, dictlike, others...) -> dictlike′
Merge key-value pairs from others
to dictlike
. Mutate dictlike
if possible.
This function "owns" dictlike
but not others
; i.e., returned value dictlike′
does not alias any of others
. For example, merge!!(Empty(Dict), other)
shallow-copies other
instead of returning other
as-is.
Method merge!!(combine::Union{Function,Type}, args...)
as an alias of mergewith!!(combine, args...)
is still available for backward compatibility.
See also mergewith!!
.
Examples
julia> using BangBang
julia> merge!!(Dict(:a => 1), Dict(:b => 0.5))
Dict{Symbol, Float64} with 2 entries:
:a => 1.0
:b => 0.5
julia> merge!!((a = 1,), Dict(:b => 0.5))
(a = 1, b = 0.5)
julia> merge!!(+, Dict(:a => 1), Dict(:a => 0.5))
Dict{Symbol, Float64} with 1 entry:
:a => 1.5
merge!!
does not own the second argument:
julia> xs = Dict(:a => 1, :b => 2, :c => 3);
julia> ys = merge!!(Empty(Dict), xs)
Dict{Symbol, Int64} with 3 entries:
:a => 1
:b => 2
:c => 3
julia> ys === xs
false
BangBang.mergewith!!
— Functionmergewith!!(combine, dictlike, others...) -> dictlike′
mergewith!!(combine)
Like merge!!(combine, dictlike, others...)
but combine
does not have to be a Function
.
This function "owns" dictlike
but not others
. See merge!!
for more details.
The curried form mergewith!!(combine)
returns the function (args...) -> mergewith!!(combine, args...)
.
BangBang.mul!!
— Functionmul!!(C, A, B, [α, β]) -> C′
BangBang.pop!!
— Functionpop!!(sequence) -> (sequence′, value)
pop!!(assoc, key) -> (assoc′, value)
pop!!(assoc, key, default) -> (assoc′, value)
Examples
julia> using BangBang
julia> pop!!([0, 1])
([0], 1)
julia> pop!!((0, 1))
((0,), 1)
julia> pop!!(Dict(:a => 1), :a)
(Dict{Symbol, Int64}(), 1)
julia> pop!!((a=1,), :a)
(NamedTuple(), 1)
julia> using StaticArrays: SVector
julia> @assert pop!!(SVector(1, 2)) === (SVector(1), 2)
BangBang.popfirst!!
— Functionpopfirst!!(sequence) -> (sequence′, value)
Examples
julia> using BangBang
julia> popfirst!!([0, 1])
([1], 0)
julia> popfirst!!((0, 1))
((1,), 0)
julia> popfirst!!((a=0, b=1))
((b = 1,), 0)
julia> using StaticArrays: SVector
julia> @assert popfirst!!(SVector(1, 2)) === (SVector(2), 1)
BangBang.push!!
— Functionpush!!(collection, items...)
Push one or more items
to collection. Create a copy of collection
if it cannot be mutated or the element type does not match.
Examples
julia> using BangBang
julia> push!!((1, 2), 3)
(1, 2, 3)
julia> push!!([1, 2], 3)
3-element Vector{Int64}:
1
2
3
julia> push!!([1, 2], 3.0)
3-element Vector{Float64}:
1.0
2.0
3.0
julia> using StaticArrays: SVector
julia> @assert push!!(SVector(1, 2), 3.0) === SVector(1.0, 2.0, 3.0)
julia> using DataFrames: DataFrame
julia> @assert push!!(DataFrame(a=[1], b=[2]), (a=3.5, b=4.5)) ==
DataFrame(a=[1.0, 3.5], b=[2.0, 4.5])
julia> using StructArrays: StructVector
julia> @assert push!!(StructVector(a=[1], b=[2]), (a=3.5, b=4.5)) ==
StructVector(a=[1.0, 3.5], b=[2.0, 4.5])
julia> using TypedTables: Table
julia> @assert push!!(Table(a=[1], b=[2]), (a=3.5, b=4.5)) ==
Table(a=[1.0, 3.5], b=[2.0, 4.5])
BangBang.pushfirst!!
— Functionpushfirst!!(collection, items...)
Examples
julia> using BangBang
julia> pushfirst!!((1, 2), 3, 4)
(3, 4, 1, 2)
julia> pushfirst!!([1, 2], 3, 4)
4-element Vector{Int64}:
3
4
1
2
julia> pushfirst!!([1, 2], 3, 4.0)
4-element Vector{Float64}:
3.0
4.0
1.0
2.0
julia> using StaticArrays: SVector
julia> @assert pushfirst!!(SVector(1, 2), 3, 4) === SVector(3, 4, 1, 2)
BangBang.rmul!!
— Functionrmul!!(A, B) -> A′
BangBang.setdiff!!
— Functionsetdiff!!(setlike, others...) -> setlike′
Return the set of elements in setlike
but not in any of the collections others
. Mutate setlike
if possible.
This function "owns" setlike
but not others
; i.e., returned value setlike′
does not alias any of others
. For example, setdiff!!(Empty(Set), other)
shallow-copies other
instead of returning other
as-is.
Examples
julia> using BangBang
julia> xs = Set([1]);
julia> ys = setdiff!!(xs, [1]); # mutates `xs` as it's possible
julia> ys == Set([])
true
julia> ys === xs # `xs` is returned
true
BangBang.setindex!!
— Functionsetindex!!(collection, value, indices...) -> collection′
Examples
julia> using BangBang
julia> setindex!!((1, 2), 10.0, 1)
(10.0, 2)
julia> setindex!!([1, 2], 10.0, 1)
2-element Vector{Float64}:
10.0
2.0
julia> using StaticArrays: SVector
julia> @assert setindex!!(SVector(1, 2), 10.0, 1) == SVector(10.0, 2.0)
BangBang.setproperties!!
— Methodsetproperties!!(value, patch::NamedTuple)
setproperties!!(value; patch...)
Examples
julia> using BangBang
julia> setproperties!!((a=1, b=2); b=3)
(a = 1, b = 3)
julia> struct Immutable
a
b
end
julia> setproperties!!(Immutable(1, 2); b=3)
Immutable(1, 3)
julia> mutable struct Mutable{T, S}
a::T
b::S
end
julia> s = Mutable(1, 2);
julia> setproperties!!(s; b=3)
Mutable{Int64, Int64}(1, 3)
julia> setproperties!!(s, b=4.0)
Mutable{Int64, Float64}(1, 4.0)
julia> s
Mutable{Int64, Int64}(1, 3)
BangBang.setproperty!!
— Functionsetproperty!!(value, name::Symbol, x)
An alias of setproperties!!(value, (name=x,))
.
BangBang.splice!!
— Functionsplice!!(sequence, i, [replacement]) -> (sequence′, item)
Examples
julia> using BangBang
julia> splice!!([1, 2, 3], 2)
([1, 3], 2)
julia> splice!!((1, 2, 3), 2)
((1, 3), 2)
julia> using StaticArrays: SVector
julia> @assert splice!!(SVector(1, 2, 3), 2) === (SVector(1, 3), 2)
BangBang.union!!
— Functionunion!!(setlike, others...) -> setlike′
Return the union of all sets in the arguments. Mutate setlike
if possible.
This function "owns" setlike
but not others
; i.e., returned value setlike′
does not alias any of others
. For example, union!!(Empty(Set), other)
shallow-copies other
instead of returning other
as-is.
Examples
julia> using BangBang
julia> xs = Set([1]);
julia> ys = union!!(xs, Set([2])); # mutates `xs` as it's possible
julia> ys == Set([1, 2])
true
julia> ys === xs # `xs` is returned
true
julia> zs = union!!(xs, Set([0.5])); # incompatible element type
julia> zs == Set([0.5, 1, 2])
true
julia> zs === xs # a new set is returned
false
union!!
does not own the second argument:
julia> xs = Set([1]);
julia> ys = union!!(Empty(Set), xs)
Set{Int64} with 1 element:
1
julia> ys === xs
false
BangBang.unique!!
— Functionunique!!(set) -> set
unique!!(sequence) -> sequence′
BangBang.@!
— Macro@! expr
Convert all supported mutating calls to double bang equivalent.
Examples
julia> using BangBang
julia> @! push!(empty!((0, 1)), 2, 3)
(2, 3)
julia> y = [1, 2];
julia> @! y .= 2 .* y
y
2-element Vector{Int64}:
2
4
julia> y = (1, 2);
julia> @! y .= 2 .* y
y
(2, 4)
BangBang.NoBang.Empty
— TypeEmpty(T)
Create a proxy of an empty container of type T
.
This is a simple API for solving problems such as:
- There is no consistent way to create an empty container given its type.
- There is no consistent way to know that nothing was appended into the container in type-domain.
Internally, this function simply works by creating a singleton container (a container with one element) using singletonof
when the first element is push!!
'ed.
Examples
julia> using BangBang
julia> push!!(Empty(Vector), 1)
1-element Vector{Int64}:
1
julia> append!!(Empty(Dict), (:a=>1, :b=>2))
Dict{Symbol, Int64} with 2 entries:
:a => 1
:b => 2
julia> using DataFrames: DataFrame
julia> @assert push!!(Empty(DataFrame), (a=1, b=2)) == DataFrame(a=[1], b=[2])
julia> using StructArrays: StructVector
julia> @assert push!!(Empty(StructVector), (a=1, b=2)) == StructVector(a=[1], b=[2])
julia> using TypedTables: Table
julia> @assert push!!(Empty(Table), (a=1, b=2)) == Table(a=[1], b=[2])
julia> using StaticArrays: SVector
julia> @assert push!!(Empty(SVector), 1) === SVector(1)
Empty(T)
object is an iterable with length 0 and element type Union{}
:
julia> collect(Empty(Vector))
Union{}[]
julia> length(Empty(Vector))
0
julia> eltype(typeof(Empty(Vector)))
Union{}
julia> Base.IteratorSize(Empty)
Base.HasLength()
julia> Base.IteratorEltype(Empty)
Base.HasEltype()
BangBang.NoBang.singletonof
— Methodsingletonof(::Type{T}, x) :: T
singletonof(::T, x) :: T
Create a singleton container of type T
.
Examples
julia> using BangBang
julia> @assert singletonof(Vector, 1) == [1]
julia> @assert singletonof(Dict, :a => 1) == Dict(:a => 1)
julia> @assert singletonof(Set, 1) == Set([1])
julia> using StructArrays: StructVector
julia> @assert singletonof(StructVector, (a=1, b=2)) == StructVector(a=[1], b=[2])
julia> using TypedTables: Table
julia> @assert singletonof(Table, (a=1, b=2)) == Table(a=[1], b=[2])
julia> using StaticArrays: SArray, SVector
julia> @assert singletonof(SArray, 1) === SVector(1)
julia> @assert singletonof(SVector, 1) === SVector(1)
BangBang.SetfieldImpl.prefermutation
— Functionprefermutation(lens::Lens) :: Lens
See also @set!!
.
BangBang.SetfieldImpl.@set!!
— Macro@set!! assignment
Like Setfield.@set
, but prefer mutation if possible.
Examples
julia> using BangBang
julia> mutable struct Mutable
a
b
end
julia> x = orig = Mutable((x=Mutable(1, 2), y=3), 4);
julia> @set!! x.a.x.a = 10;
julia> @assert x.a.x.a == orig.a.x.a == 10
BangBang.Extras
— ModuleBangBang.Extras
BangBang
APIs that have no counterparts in Base
.
BangBang.Extras.modify!!
— Functionmodify!!(f, dictlike, key) -> (dictlike′, ret)
Lookup and then update, insert or delete in one go. If supported (e.g., when dictlike isa Dict
), it is done without re-computing the hash. Immutable containers like NamedTuple
is also supported.
The callable f
must accept a single argument of type Union{Some{valtype(dictlike)}, Nothing}
. The value Some(dictlike[key])
is passed to f
if haskey(dictlike, key)
; otherwise nothing
is passed.
If f
returns nothing
, corresponding entry in the dictionary dictlike
is removed. If f
returns non-nothing
value x
, key => something(x)
is inserted to dictlike
(equivalent to dictlike[key] = something(x)
but more efficient).
modify!!
returns a 2-tuple (dictlike′, ret)
where dictlike′
is an updated version of dictlike
(which may be identical to dictlike
) and ret
is the returned value of f
.
This API is inspired by Control.Lens.At
of Haskell's lens library.
Examples
julia> using BangBang.Extras: modify!!
julia> dict = Dict("a" => 1);
julia> dict′, ret = modify!!(dict, "a") do val
Some(something(val, 0) + 1)
end;
julia> ret
Some(2)
julia> dict === dict′
true
julia> dict
Dict{String, Int64} with 1 entry:
"a" => 2
julia> dict = Dict();
julia> modify!!(dict, "a") do val
Some(something(val, 0) + 1)
end;
julia> dict
Dict{Any, Any} with 1 entry:
"a" => 1
julia> modify!!(_ -> nothing, dict, "a");
julia> dict
Dict{Any, Any}()
Discussion