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 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.

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.

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

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.