So I figured I’d try and see if I could make this work with @enum
s. Short answer: yes. no. (see bottom.)
Code which creates specialized methods of Val
for type-stable(ish) dynamic dispatch on enums:
@generated Val(var"#"::T) where T<:Base.Enums.Enum = let ifex(enum)=:(if var"#"≡$enum; Val{$enum}() end),
enums = instances(T), ret = ifex(enums[1]), expr = ret
for enum = enums[2:end]; expr = last(push!(expr.args, ifex(enum))) end
expr.head, expr.args = expr.args[end].head, expr.args[end].args
ret
end
Sample code which utilizes this:
julia> using BenchmarkTools
@enum Foo a b c
foo(x) = foo(Val(Foo(x)), x)
foo(::Val{a}, x) = x+1
foo(::Val{b}, x) = x
foo(::Val{c}, x) = x-1
println("Before:")
@btime foo(rand(0:2))
@generated Val(var"#"::T) where T<:Base.Enums.Enum = let ifex(enum)=:(if var"#"≡$enum; Val{$enum}() end),
enums = instances(T), ret = ifex(enums[1]), expr = ret
for enum = enums[2:end]; expr = last(push!(expr.args, ifex(enum))) end
expr.head, expr.args = expr.args[end].head, expr.args[end].args
ret
end
println("After:")
@btime foo(rand(0:2));
Before:
80.372 ns (1 allocation: 16 bytes)
After:
11.712 ns (0 allocations: 0 bytes)
Here’s a plot showing how the performance of this relates to how many elements are in the enumeration:
code used to generate this
Note: I had to close and re-open the Julia process each time.
using BenchmarkTools
begin local globcnt = 0
testenum(n) = let Foo=Symbol(:Foo, globcnt+=1), ids=ntuple(n->Symbol(:foo, globcnt+=1), n); quote
@enum $Foo $(ids...)
foo(x) = foo(Val($Foo(x)), x)
$((:(foo(::Val{$(id)}, x) = x+1) for id in ids)...)
println("Timing before/after specializing Val (n = $($n))")
@btime foo(rand(0:$(length(ids)-1)))
@generated Val(var"#"::T) where T<:Base.Enums.Enum = let ifex(enum)=:(if var"#"≡$enum; Val{$enum}() end),
enums = instances(T), ret = ifex(enums[1]), expr = ret
for enum = enums[2:end]; expr = last(push!(expr.args, ifex(enum))) end
expr.head, expr.args = expr.args[end].head, expr.args[end].args
ret
end
@btime foo(rand(0:$(length(ids)-1)))
end end |> eval
end
testenum(4)
Edit:
Looks like I was benefitting from odd execution order; if you place the Val
specialization before the @enum
declaration, then you run into world age problems.