This package offers a minimal interface to work efficiently with a Union of types wrapped into a struct
by allowing to enforce union-splitting at call site.
Two main macros provide the backbone of this package: @wrapped and @unionsplit. The first accepts any
parametric struct which has a single field union::Union but, apart from that, it supports any standard
struct feature as e.g. inner constructors. @unionsplit instead automatically executes a function performing
union-splitting on the wrapped union arguments to make the call type-stable.
julia> using WrappedUnions
julia> @wrapped struct X <: WrappedUnion
union::Union{Bool, Int, Vector{Bool}, Vector{Int}}
end
julia> xs = (X(false), X(1), X([true, false]), X([1,2]))
(X(false), X(1), X(Bool[1, 0]), X([1, 2]))
julia> splitsum(x) = @unionsplit sum(x)
splittedsum (generic function with 1 method)
julia> splitsum.(xs)
(0, 1, 1, 3)
julia> unwrap(xs[3])
2-element Vector{Bool}:
1
0
julia> iswrappedunion(typeof(xs[1]))
true
julia> uniontype(typeof(xs[1]))
Union{Bool, Int64, Vector{Bool}, Vector{Int64}}Let's verify that splitsum has been accurately inferred:
julia> @code_warntype splitsum.(xs)
MethodInstance for (::var"##dotfunction#230#1")(::NTuple{4, X})
from (::var"##dotfunction#230#1")(x1) @ Main none:0
Arguments
#self#::Core.Const(var"##dotfunction#230#1"())
x1::NTuple{4, X}
Body::NTuple{4, Int64}
1 ─ %1 = Base.broadcasted(Main.splitsum, x1)::Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple}, Nothing, typeof(splitsum), Tuple{NTuple{4, X}}}
│ %2 = Base.materialize(%1)::NTuple{4, Int64}
└── return %2What if we used just
julia> xs = (false, 1, [true, false], [1,2])
(false, 1, Bool[1, 0], [1, 2])then
julia> @code_warntype sum.(xs)
MethodInstance for (::var"##dotfunction#230#1")(::Tuple{Bool, Int64, Vector{Bool}, Vector{Int64}})
from (::var"##dotfunction#230#1")(x1) @ Main none:0
Arguments
#self#::Core.Const(var"##dotfunction#230#1"())
x1::Tuple{Bool, Int64, Vector{Bool}, Vector{Int64}}
Body::NTuple{4, Any}
1 ─ %1 = Main.sum::Core.Const(sum)
│ %2 = Base.broadcasted(%1, x1)::Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple}, Nothing, typeof(sum), Tuple{Tuple{Bool, Int64, Vector{Bool}, Vector{Int64}}}}
│ %3 = Base.materialize(%2)::NTuple{4, Any}
└── return %3Notice the NTuple{4, Any} instead of NTuple{4, Int64}.
Consider also that @unionsplit allows to easily forward calls of any function, such as getproperty or setproperty!
as shown below
julia> using WrappedUnions
julia> mutable struct A x::Int end
julia> mutable struct B x::Int end
julia> @wrapped struct Y <: WrappedUnion
union::Union{A, B}
end
julia> Base.getproperty(y::Y, name::Symbol) = @unionsplit Base.getproperty(y, name)
julia> Base.setproperty!(y::Y, name::Symbol, x) = @unionsplit Base.setproperty!(y, name, x)
julia> y = Y(A(1))
Y(A(1))
julia> y.x
1
julia> y.x = 2
2What if the function is inherently type-unstable? Well, just use another wrapped union as output!
julia> @wrapped struct Z <: WrappedUnion
union::Union{Float64, Vector{Float64}}
end
julia> f(x) = x / true;
julia> f(x::X) = Z(@unionsplit f(x));
julia> xs = (X(false), X(1), X([true, false]), X([1,2]))
(X(false), X(1), X(Bool[1, 0]), X([1, 2]))
julia> f.(xs) # this is now type-stable
(Z(0.0), Z(1.0), Z([1.0, 0.0]), Z([1.0, 2.0]))- WrappedUnion -> Abstract type which could be optionally used as
a supertype of wrapped unions.
- @wrapped struct ... end -> Creates a wrapped union.
- unionsplit(f::Union{Type,Function},
args::Tuple,
kwargs::NamedTuple) -> Executes the function performing union-splitting
on the wrapped union arguments and keyword arguments.
- @unionsplit f(args...; kwargs...) -> Calls `unionsplit(f, args, kwargs)`.
- unwrap(wu) -> Returns the instance contained in the wrapped
union.
- iswrappedunion(::Type) -> Returns true if the type is a wrapped union.
- uniontype(::Type) -> Returns the internal union inside the wrapped union.
For more information, see the docstrings.
These are packages with similar functionalities:
Though, WrappedUnions.jl is the one offering, in the eye of its author, the most transparent, simple and flexible
interface.
Contributions are welcome! If you encounter any issues, have suggestions for improvements, or would like to add new features, feel free to open an issue or submit a pull request.