# Would it be possible to infer \`Val{::Bool)\` as \`Union{Val{true}, Val{false}}\`?

**URL:** <https://discourse.julialang.org/t/would-it-be-possible-to-infer-val-bool-as-union-val-true-val-false/94728>\
**Category:** General Usage\
**Tags:** question, val\
**Created:** [February 16, 2023, 3:59pm UTC](https://discourse.julialang.org/t/would-it-be-possible-to-infer-val-bool-as-union-val-true-val-false/94728 "2023-02-16T15:59:46Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [February 16, 2023, 3:59pm UTC](https://discourse.julialang.org/t/would-it-be-possible-to-infer-val-bool-as-union-val-true-val-false/94728/1 "2023-02-16T15:59:46Z")

</div>

Consider the function

```julia
julia> f(x::Bool) = Val(x)
f (generic function with 1 method)

julia> @code_typed f(true)
CodeInfo(
1 ─ %1 = Core.apply_type(Base.Val, x)::Type{Val{_A}} where _A
│ %2 = %new(%1)::Val
└── return %2
) => Val

```

However, we know that the return type may only be one of `Val{true}` or `Val{false}`. Splitting the result manually (or using `ValSplit.jl`) seems to achieve the intended result:

```julia
julia> g(x::Bool) = x ? Val(true) : Val(false)
g (generic function with 1 method)

julia> @code_typed g(true)
CodeInfo(
1 ─ goto #3 if not x
2 ─ return $(QuoteNode(Val{true}()))
3 ─ return $(QuoteNode(Val{false}()))
) => Union{Val{true}, Val{false}}

```

Since `Bool` is known to have only two values, I wonder if such an inference seems worth it to avoid dynamic dispatch?

---

<div class="post-metadata">

**Author:** ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)\
**Post date:** [February 16, 2023, 4:48pm UTC](https://discourse.julialang.org/t/would-it-be-possible-to-infer-val-bool-as-union-val-true-val-false/94728/2 "2023-02-16T16:48:13Z")

</div>

It appears this would be as simple as defining a method

```julia
Val(x::Bool) = x ? Val{true}() : Val{false}()

```

This exceptional case is simple to define and I wouldn’t expect negative repercussions elsewhere. But it would be the first specialization of `Val` so it should get some debate.

I was originally skeptical of what difference this might make, since `Val`s are mostly used for static branching (via dispatch) and branching with 2 outcomes can often be inferred well (don’t we do some sort of union-splitting on functions with few methods?). However, I made this benchmark and here are some (somewhat idealized, since the target functions are trivial) possible results of this proposal:

```julia
# Julia v1.8.0

ValBool(x::Bool) = x ? Val{true}() : Val{false}()
getvaluetypestable(::Val{true}) = 1
getvaluetypestable(::Val{false}) = 0
getvaluetypeunstable(::Val{true}) = Val{1}()
getvaluetypeunstable(::Val{false}) = Val{0}()

using BenchmarkTools

arg = 1:1000;
@btime map(_->getvaluetypestable(Val(true)),$arg); # fully inferred baseline
# 571.186 ns (1 allocation: 7.94 KiB)
@btime map(_->getvaluetypestable(Val(rand(Bool))),$arg);
# 158.000 μs (1 allocation: 7.94 KiB)
@btime map(_->getvaluetypeunstable(Val(rand(Bool))),$arg);
# 158.900 μs (2 allocations: 7.98 KiB)
@btime map(_->getvaluetypestable(ValBool(true)),$arg); # fully inferred baseline
# 574.713 ns (1 allocation: 7.94 KiB)
@btime map(_->getvaluetypestable(ValBool(rand(Bool))),$arg);
# 1.170 μs (1 allocation: 7.94 KiB)
@btime map(_->getvaluetypeunstable(ValBool(rand(Bool))),$arg);
# 1.940 μs (2 allocations: 7.98 KiB)

```

Looks like this could be a nice win for unstable `Val(::Bool)`. Obviously, one should strive for static inference where possible but in some cases it isn’t.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 16, 2023, 7:26pm UTC](https://discourse.julialang.org/t/would-it-be-possible-to-infer-val-bool-as-union-val-true-val-false/94728/3 "2023-02-16T19:26:40Z")

</div>

Sorry for the screenshot, but I’m on my phone and couldn’t copy/paste from [Tryjulia](https://julialang.org/learning/tryjulia/)

 ![image](https://global.discourse-cdn.com/julialang/original/3X/6/a/6acea275a66a92d6c4733dbd1ac455e809c2ff5c.jpeg)

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 20, 2023, 8:25am UTC](https://discourse.julialang.org/t/would-it-be-possible-to-infer-val-bool-as-union-val-true-val-false/94728/4 "2023-02-20T08:25:05Z")

</div>

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:

```julia
@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
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:

 ![image](https://global.discourse-cdn.com/julialang/original/3X/3/d/3df02100aa85573e21e4ddea663c634a442fd264.png)

> **code used to generate this**
>
> Note: I had to close and re-open the Julia process each time.
> 
> ```julia
> 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.
