This package allows to combine multiple heterogeneous types in a single one. This helps to write
type-stable code by avoiding Union
performance drawbacks when many types are unionized. Another
aim of this library is to provide a syntax as similar as possible to standard Julia
structs to facilitate its integration within other libraries.
The @sumtype
macro takes inspiration from SumTypes.jl,
but it offers a much more simple and idiomatic interface. Working with it is almost like working with Union
types.
To define a sum type you can just take an arbitrary number of types and enclose them in it like so:
julia> using DynamicSumTypes
julia> abstract type AbstractS end
julia> struct A{X}
x::X
end
julia> mutable struct B{Y}
y::Y
end
julia> struct C
z::Int
end
julia> @sumtype S{X}(A{X},B{Int},C) <: AbstractS
Then constructing instances is just a matter of enclosing the type constructed in the predefined sum type:
julia> a = S(A(1))
S{Int64}(A{Int64}(1))
julia> b = S{Int}(B(1))
S{Int64}(B{Int64}(1))
julia> c = S{Int}(C(1))
S{Int64}(C(1))
a different syntax is also provided for convenience:
julia> a = S'.A(1)
S{Int64}(A{Int64}(1))
julia> b = S{Int}'.B(1)
S{Int64}(B{Int64}(1))
julia> c = S{Int}'.C(1)
S{Int64}(C(1))
This works like if they were normal Julia types:
julia> a.x
1
julia> b.y = 3
3
For this, you can simply access the variant inside the sum type and then dispatch on it:
julia> f(x::S) = f(variant(x))
julia> f(x::A) = 1
julia> f(x::B) = 2
julia> f(x::C) = 3
julia> f(a)
1
julia> f(b)
2
julia> f(c)
3
Benchmark code
using BenchmarkTools
using DynamicSumTypes
struct A end
struct B end
struct C end
struct D end
struct E end
struct F end
@sumtype S(A, B, C, D, E, F)
f(s::S) = f(variant(s));
f(::A) = 1;
f(::B) = 2;
f(::C) = 3;
f(::D) = 4;
f(::E) = 5;
f(::F) = 6;
vals = rand((A(), B(), C(), D(), E(), F()), 1000);
tuple_manytypes = Tuple(vals);
vec_manytypes = collect(Union{A, B, C, D, E, F}, vals);
iter_manytypes = (x for x in vec_manytypes);
tuple_sumtype = Tuple(S.(vals));
vec_sumtype = S.(vals);
iter_sumtype = (x for x in vec_sumtype)
@benchmark sum($f, $tuple_manytypes)
@benchmark sum($f, $tuple_sumtype)
@benchmark sum($f, $vec_manytypes)
@benchmark sum($f, $vec_sumtype)
@benchmark sum($f, $iter_manytypes)
@benchmark sum($f, $iter_sumtype)
julia> @benchmark sum($f, $tuple_manytypes)
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
Range (min … max): 81.092 μs … 1.267 ms ┊ GC (min … max): 0.00% … 90.49%
Time (median): 85.791 μs ┊ GC (median): 0.00%
Time (mean ± σ): 87.779 μs ± 18.802 μs ┊ GC (mean ± σ): 0.35% ± 1.67%
▂ ▃▇█▆▆▅▃▂▂▂▁▁ ▂
█████████████████▇▇▇▅▆▅▄▅▅▅▄▄▄▄▄▄▅▄▄▄▄▄▄▄▃▄▅▅▄▃▅▄▅▅▅▄▅▅▄▅▅▅ █
81.1 μs Histogram: log(frequency) by time 130 μs <
Memory estimate: 13.42 KiB, allocs estimate: 859.
julia> @benchmark sum($f, $tuple_sumtype)
BenchmarkTools.Trial: 10000 samples with 107 evaluations.
Range (min … max): 770.514 ns … 4.624 μs ┊ GC (min … max): 0.00% … 0.00%
Time (median): 823.514 ns ┊ GC (median): 0.00%
Time (mean ± σ): 826.188 ns ± 42.968 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
█ ▁ ▂ ▂
▂▁▂▂▂▂▂▂▂▂▂▂▂▂▂▂▇▂█▃██▃█▅█▄▂██▂█▅▃▃▂▂▃▂▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
771 ns Histogram: frequency by time 900 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
julia> @benchmark sum($f, $vec_manytypes)
BenchmarkTools.Trial: 10000 samples with 207 evaluations.
Range (min … max): 367.164 ns … 566.816 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 389.280 ns ┊ GC (median): 0.00%
Time (mean ± σ): 390.919 ns ± 9.984 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
▁ ▇▁ ▃ ▁ █ ▂
▂▂▃▂▁▁▂▁▁▂▂▁▂▂▄█▃██▃█▃▄█▂█▇▃█▅▃█▃▃▃▂▃▂▂▃▂▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
367 ns Histogram: frequency by time 424 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
julia> @benchmark sum($f, $vec_sumtype)
BenchmarkTools.Trial: 10000 samples with 254 evaluations.
Range (min … max): 297.016 ns … 464.575 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 308.811 ns ┊ GC (median): 0.00%
Time (mean ± σ): 306.702 ns ± 7.518 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
▁ ▆█▅ ▅█▆▁▁▅▄ ▂▂ ▁ ▁▄▄▁ ▂▁ ▂
▇██████▇▅▅▄▅▅▄▄▄▃▄▄▅▅▅▆████████▇▆██▆▅▇██▅███████▇██▇▃▄▅▆▅▅▄▃▅ █
297 ns Histogram: log(frequency) by time 326 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
julia> @benchmark sum($f, $iter_manytypes)
BenchmarkTools.Trial: 10000 samples with 10 evaluations.
Range (min … max): 1.323 μs … 3.407 μs ┊ GC (min … max): 0.00% … 0.00%
Time (median): 1.390 μs ┊ GC (median): 0.00%
Time (mean ± σ): 1.389 μs ± 54.987 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
▅▄▁▂ ▃█▇▇
▃▄▆████▅▄▇████▆▇▆▅▄▄▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▁▂▁▂▂▂▂▁▁▂▂▁▂▂▂▂▂▂▂▂▂ ▃
1.32 μs Histogram: frequency by time 1.67 μs <
Memory estimate: 0 bytes, allocs estimate: 0.
julia> @benchmark sum($f, $iter_sumtype)
BenchmarkTools.Trial: 10000 samples with 258 evaluations.
Range (min … max): 310.236 ns … 370.112 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 318.971 ns ┊ GC (median): 0.00%
Time (mean ± σ): 319.347 ns ± 5.859 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
▁ ▄▇▆▁▃▆█▃ ▃▆▅ ▄▆▄ ▃▆▇▃▁▄▇▇▃▁▂▅▄▁ ▁▂▁ ▁ ▁▁ ▁ ▃
▅█▂▆████████▇███▅███████████████████▇██████████████████▇▅█▆▇▅ █
310 ns Histogram: log(frequency) by time 338 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
These benchmarks have been run on Julia 1.11
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.