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.