BangBang.jl

BangBang.BangBangModule

BangBang

Stable Dev Build Status Codecov Coveralls 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 function implement the "widening" fallback for the case 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

source
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 Array{Int64,1}:
 3
source
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 Array{Int64,1}:
 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 Array{Int64,1}:
 1
 2
 3

julia> ys === xs
false
source
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 Array{Float64,1}:
 1.0
 0.5

julia> finish!(append!!(collector(), (x for x in Any[1, 2.0, 3im])))
3-element Array{Complex{Float64},1}:
 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 Array{Float64,1}:
 1.0
 2.0
 3.0
source
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
source
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 Array{Int64,1}:
 1
 3

julia> using StaticArrays: SVector

julia> @assert deleteat!!(SVector(1, 2, 3), 2) === SVector(1, 3)
source
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)
0-element Array{Int64,1}

julia> xs
0-element Array{Int64,1}

julia> using StaticArrays: SVector

julia> @assert empty!!(SVector(1, 2)) == SVector{0, Int}()
source
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 Array{Float64,1}:
 2.0
 2.5
 3.0

julia> xs === ys  # mutated
true

julia> xs = Vector{Union{}}(undef, 3);

julia> ys = materialize!!(xs, bc)
3-element Array{Float64,1}:
 2.0
 2.5
 3.0

julia> xs === ys
false
source
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
source
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...).

source
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)
source
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)
source
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 Array{Int64,1}:
 1
 2
 3

julia> push!!([1, 2], 3.0)
3-element Array{Float64,1}:
 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])
source
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 Array{Int64,1}:
 3
 4
 1
 2

julia> pushfirst!!([1, 2], 3, 4.0)
4-element Array{Float64,1}:
 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)
source
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, 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
source
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 Array{Float64,1}:
 10.0
  2.0

julia> using StaticArrays: SVector

julia> @assert setindex!!(SVector(1, 2), 10.0, 1) == SVector(10.0, 2.0)
source
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)
source
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)
source
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
source
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 Array{Int64,1}:
 2
 4

julia> y = (1, 2);

julia> @! y .= 2 .* y
       y
(2, 4)
source
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 Array{Int64,1}:
 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))
0-element Array{Union{},1}

julia> length(Empty(Vector))
0

julia> eltype(typeof(Empty(Vector)))
Union{}

julia> Base.IteratorSize(Empty)
Base.HasLength()

julia> Base.IteratorEltype(Empty)
Base.HasEltype()
source
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)
source
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
source
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} with 0 entries

Discussion

source