BangBang.BangBangModule

BangBang

Stable Dev Codecov Aqua QA GitHub commits since latest release (branch)

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.Extras.broadcast_inplace!!Function
broadcast_inplace!!(f, inputoutput, As...) -> inputoutput′

A mutate-or-widen version of inputoutput .= f.(inputoutput, As...).

BangBang.__append!!__Function
__append!!__(dest::CustomType, src) -> dest′

This is an overload interface for append!!. This function must dispatch on the first argument and implemented by the owner of the type of the first argument.

BangBang.__appendto!!__Function
__appendto!!__(dest, src::CustomType) -> dest′

This is an overload interface for append!!. This function must dispatch on the second argument and implemented by the owner of the type of second first argument. This function is used when there is no specific implementation for __append!!__ is found.

BangBang.add!!Function
add!!(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!!Function
append!!(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!!Function
broadcast!!(f, dest, As...) -> dest′

A mutate-or-widen version of dest .= f.(As...).

BangBang.collectorFunction
collector(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!!Function
delete!!(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!!Function
deleteat!!(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!!Function
empty!!(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!Function
finish!(c::AbstractCollector) -> data::AbstractVector

Extract the data collected in the collector c.

See collector.

BangBang.implementsFunction
implements(f!, x) :: Bool

true if in-place function f! can mutate x.

Examples

julia> using BangBang: implements

julia> implements(push!, Vector)
true

julia> implements(push!, [])  # works with instances, too
true

julia> implements(push!, Tuple)
false

julia> using StaticArrays

julia> implements(push!, SVector)
false

julia> implements(setindex!, SVector)
false

julia> implements(push!, MVector)
false

julia> implements(setindex!, MVector)
true
BangBang.intersect!!Function
intersect!!(setlike, others...) -> setlike′

Return the set of elements in setlike and in all 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, other = Set([1]); intersect!!(Set([1, 2]), other) does not return other as-is.

Examples

julia> using BangBang

julia> xs = Set([1, 2]);

julia> ys = intersect!!(xs, [1]);  # mutates `xs` as it's possible

julia> ys == Set([1])
true

julia> ys === xs  # `xs` is returned
true
BangBang.materialize!!Method
materialize!!(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.mayFunction
may(mutate!, args...)

Call mutate!(args...) if possible; fallback to the out-of-place version if not.

BangBang.merge!!Function
merge!!(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!!Function
mergewith!!(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.pop!!Function
pop!!(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!!Function
popfirst!!(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.possibleFunction
possible(f!, args...) :: Bool

Check if f!(args...) is possible.

Examples

julia> using BangBang: possible

julia> possible(push!, Int[], 1)
true

julia> possible(push!, Int[], 0.5)
false
BangBang.push!!Function
push!!(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!!Function
pushfirst!!(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.resize!!Function
resize!!(vector::AbstractVector, n::Integer) -> vector′
BangBang.setdiff!!Function
setdiff!!(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, other = Set([]); setdiff!!(Empty(Set), other) does not return 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!!Function
setindex!!(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!!Method
setproperties!!(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!!Function
setproperty!!(value, name::Symbol, x)

An alias of setproperties!!(value, (name=x,)).

BangBang.splice!!Function
splice!!(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.symdiff!!Function
symdiff!!(setlike, others...) -> setlike′

Return the set of elements that occur an odd number of times in setlike and the others collections. Mutate setlike if possible.

This function "owns" setlike but not others; i.e., returned value setlike′ does not alias any of others. For example, symdiff!!(Empty(Set), other) shallow-copies other instead of returning other as-is.

Examples

julia> using BangBang

julia> xs = Set([1, 2]);

julia> ys = symdiff!!(xs, [2, 3]);  # mutates `xs` as it's possible

julia> ys == Set([1, 3])
true

julia> ys === xs  # `xs` is returned
true
BangBang.union!!Function
union!!(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.@!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.EmptyType
Empty(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.singletonofMethod
singletonof(::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.ExtrasModule
BangBang.Extras

BangBang APIs that have no counterparts in Base.

BangBang.Extras.modify!!Function
modify!!(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

BangBang.AccessorsImpl.@set!!Macro
@set!! assignment

Like Accessors.@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