How to constraint the inner type of `Val` by an abstract type

How can I constraint the parametric type within Val by an abstract type?

Consider the following function

using Unitful: Frequency, Hz

function foo(q::Val{Q}) where Q <: Frequency
    return q
end

I’d like to Q to be of type Frequency.

Unfortunately, the above code errors with:

julia> foo(Val(1.0Hz))
ERROR: MethodError: no method matching foo(::Val{1.0 Hz})
The function `foo` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  foo(::Val{Q}) where Q<:(Union{Quantity{T, 𝐓^-1, U}, Level{L, S, Quantity{T, 𝐓^-1, U}} where {L, S}} where {T, U})

Context: I need to use Val in order to unroll a loop.

You can’t dispatch on values, you must check it manually:

function foo(::Val{q})
    q isa Frequency || error("argument must be Val(Frequency)")
    ... do stuff to q ...
end
1 Like

Well, yes, that’s what Val is for. But you can’t dispatch on values and their types at the same time.

The suggested syntax doesn’t make sense here. The operator <: is for subtype relationships. Int32 <: Integer makes sense, but 32 <: Integer does not. It would have to be something like

foo(::Val{Q}) where Q isa Frequency

(you normally don’t include the q in this case, since Q is the actual value.

To be clear, this isn’t valid Julia right now. I think it could be done because isa is a Core function, but I’m not certain. For now, multiple methods that constrain the Val parameter’s type can done by forwarding to a function call on the unwrapped value:

julia> begin
       foo(::Val{q}) where q = _foo(q)
       _foo(q::Integer) = 0
       _foo(q::AbstractFloat) = 0.0
       end;

julia> foo(Val(1)), foo(Val(1.0))
(0, 0.0)
1 Like

Related discussion: constraints on non-type as type parameters · Issue #9580 · JuliaLang/julia · GitHub

2 Likes