BangBang.jl

BangBang.BangBangModule

BangBang

Stable Dev Codecov Aqua QA GitHub commits since tagged version

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!!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.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.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.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.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.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
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