Using parametric types for Complex{T}

I am writing some signal processing code, and I want to be able to call it for both real and complex signals, based on either Float32 or Float64. I make no use of the real and imaginary parts of the signals (the function is a simple interpolator using Lagrange polynomials).

Right now I define my functions as

function foo(x::Vector{T}, t::T) where T <: AbstractFloat
  tmp = T(0)
  ...
end

function foo(x::Vector{Complex{T}}, t::T) where T <: AbstractFloat
  tmp = T(0)
  ...
end

where the complex version is identical to the real. This is not a good way to organise ones code, so I wonder if there is any way I can write this function once for all four types? I could of course not use typing in the function argument list, but then I could get a mix of doubles and floats as arguments. Any ideas or hints?

(I guess this is one example of type traits having been useful)

You can do something like:

function foo(x::Union{Vector{T}, Vector{Complex{T}}}, t::T) where T <: Union{Float32, Float64}
    tmp = T(0)
    ...
end

Or make it more readable with a definition:

const Signal{T} = Union{Vector{T}, Vector{Complex{T}}}

function foo(x::Signal{T}, t::T}) where T <: Union{Float32, Float64}
    tmp = T(0)
    ...
end
1 Like

I would rather use

const Signal{T} = Union{Vector{T}, Vector{Complex{T}}}

function foo(x::Signal{T}, t::T}) where T
    tmp = T(0)
    ...
end

To avoid over-contraining : if other types can pass by you increase the value of your code.

1 Like

Thank you - that worked out perfectly.

I really should have another look at Julia Unions, as I was under the impression that they worked much the same way as in C for primitive types only.

1 Like

I see what you mean, but in this particular case I would probably do eg. Integer implementations slightly differently due to under- and overflows etc. One of the purposes of my code is to simulate implementations on embedded hw, so that is why I need it to be eg. pure Float32 in those cases so that I don’t miss any round-off effects somewhere…

AbstractFloat covers four types of floats which will be used identically.

1 Like

I would rather cast the second argument to the eltype of the array if the only need is to avoid mixing types:

function foo(x::Vector{T}, t_::Real) where {T<:Number}
    R = real(T)
    t = convert(R, t_)
    tmp = zero(T)
    ...
end

(and add specific integer implementations if needed)

1 Like