Dispatch on a set of value type parameters

I have a type which has an Int value parameter, let’s represent the type like this:

struct T{m}
  some_fields
end

I want to define a method for a function f that takes a T value, but only if m is part of a certain finite set, for example if m is either 2, 3 or 4. However I also need access to m within the function.

This is the cleanest solution I could think of, however I wonder whether it’s possible to have m as a type parameter within the f method:

m_val(::Type{T{m}}) where {m} = m

f(v::V) where {V <: Union{T{2}, T{3}, T{4}}} = 7*m_val(V)

Another possible solution would be to have a separate method for each value of m, which seems like it would be horrible due to code duplication.

You may be able to play around with Value types but I think just adding m as a field simplifies things. You can keep the Int Type if you want for other methods. For example:

struct newType{M}
    m::Int
    A::Array{Int,M} #just some random field that depends on m (M==m)

    function newType(M)
        A = zeros(Int, ntuple(x->3,M)) #each axis has length 3
        new{M}(M,A)
    end
end

Now you can create a function that takes in any version of the type and use m:

f(v::newType, finiteSet) = v.m ∈ finiteSet ? 7v.m : error("m not in set")

If we make some instances of newType we can test f:

julia> v3 = newType(3);

julia> v8 = newType(8);

julia> typeof(v3)
newType{3}

julia> typeof(v8)
newType{8}

julia> finiteSet = [2,3,4];

julia> f(v3,finiteSet)
21

julia> f(v8,finiteSet)
ERROR: m not in set

I’m not an expert on types so this might be bad advice :person_shrugging: but it avoids code duplication and works for any m.

Maybe I should have said this explicitly when asking the question, but the point of using compile-time dispatch is performance. Your solution wouldn’t have cut it because f was supposed to be called within hot loops.

The solution would be to use StaticNumbers and include m as a field in struct T something like this:

struct T{m}
  some_fields
  mm::StaticInteger{m}
end

Note:

julia> Base.issingletontype(StaticInteger{3})
true
1 Like

It seems like you’re already in good shape, and you’re just looking for ways to avoid polluting the global namespace with a new function that’s specific to f.

You can achieve this by declaring a local function:

julia> struct T{m} end

julia> f(v::V) where {V<:Union{T{2},T{3},T{4}}} =
           let mval(::Type{T{m}}) where {m} = m
               7*mval(V)
           end

julia> f(T{2}())
14

Another approach would be to access the property directly:

julia> f(v::V) where {V<:Union{T{2},T{3},T{4}}} = 7*V.parameters[1]
f (generic function with 1 method)

julia> f(T{2}())
14

but this isn’t Julian. I don’t know of any method for accessing a type’s parameters; it’d be nice if we had parametersof(::Type{T}) where T = Tuple(T.parameters) but we don’t.

Hm this library is pretty cool. This looks like a good solution too.

1 Like

Also, if you want a more programmatic solution, you can use the @generated macro to produce different function body expressions depending on the argument types:

julia> @generated f(v::T{m}) where m = let
           if m ∈ (2, 3, 4)  quote 7*$m end
           else  quote throw(string($m)*" not supported") end
           end
       end
f (generic function with 1 method)

julia> f(T{1}())
ERROR: "1 not supported"

julia> f(T{2}())
14
1 Like

Ah makes sense. I’ll also have to look at StaticNumbers as well. I’m curious what the application is for this setup?

My goal (since abandoned because of various reasons) was to write an iterator that is a Cartesian power of another, wrapped, iterator. So basically a special case of the Cartesian product iterator from Base.Iterators.

So instead of

struct T{m}
  some_fields
end

I had something like this:

struct PowerIterator{m, It <: Any}
  wrapped_iterator::It
end

and I asked the question while trying to write specialized implementations for Base.iterate for a few small m, where m is the exponent in the Cartesian power.

1 Like