Can I use a subtype of a function parameter in the function definition?

486 views Asked by At

I would like to use a subtype of a function parameter in my function definition. Is this possible? For example, I would like to write something like:

g{T1, T2<:T1}(x::T1, y::T2) = x + y

So that g will be defined for any x::T1 and any y that is a subtype of T1. Obviously, if I knew, for example, that T1 would always be Number, then I could write g{T<:Number}(x::Number, y::T) = x + y and this would work fine. But this question is for cases where T1 is not known until run-time.

Read on if you're wondering why I would want to do this:

A full description of what I'm trying to do would be a bit cumbersome, but what follows is a simplified example.

I have a parameterised type, and a simple method defined over that type:

type MyVectorType{T}
    x::Vector{T}
end
f1!{T}(m::MyVectorType{T}, xNew::T) = (m.x[1] = xNew)

I also have another type, with an abstract super-type defined as follows

abstract MyAbstract
type MyType <: MyAbstract ; end

I create an instance of MyVectorType with vector element type set to MyAbstract using:

m1 = MyVectorType(Array(MyAbstract, 1))

I now want to place an instance of MyType in MyVectorType. I can do this, since MyType <: MyAbstract. However, I can't do this with f1!, since the function definition means that xNew must be of type T, and T will be MyAbstract, not MyType.

The two solutions I can think of to this problem are:

f2!(m::MyVectorType, xNew) = (m.x[1] = xNew)
f3!{T1, T2}(m::MyVectorType{T1}, xNew::T2) = T2 <: T1 ? (m.x[1] = xNew) : error("Oh dear!")

The first is essentially a duck-typing solution. The second performs the appropriate error check in the first step.

Which is preferred? Or is there a third, better solution I am not aware of?

1

There are 1 answers

0
mbauman On BEST ANSWER

The ability to define a function g{T, S<:T}(::Vector{T}, ::S) has been referred to as "triangular dispatch" as an analogy to diagonal dispatch: f{T}(::Vector{T}, ::T). (Imagine a table with a type hierarchy labelling the rows and columns, arranged such that the super types are to the top and left. The rows represent the element type of the first argument, and the columns the type of the second. Diagonal dispatch will only match the cells along the diagonal of the table, whereas triangular dispatch matches the diagonal and everything below it, forming a triangle.)

This simply isn't implemented yet. It's a complicated problem, especially once you start considering the scoping of T and S outside of function definitions and in the context of invariance. See issue #3766 and #6984 for more details.


So, practically, in this case, I think duck-typing is just fine. You're relying upon the implementation of myVectorType to do the error checking when it assigns its elements, which it should be doing in any case.

The solution in base julia for setting elements of an array is something like this:

f!{T}(A::Vector{T}, x::T) = (A[1] = x)
f!{T}(A::Vector{T}, x) = f!(A, convert(T, x))

Note that it doesn't worry about the type hierarchy or the subtype "triangle." It just tries to convert x to T… which is a no-op if x::S, S<:T. And convert will throw an error if it cannot do the conversion or doesn't know how.


UPDATE: This is now implemented on the latest development version (0.6-dev)! In this case I think I'd still recommend using convert like I originally answered, but you can now define restrictions within the static method parameters in a left-to-right manner.

julia> f!{T1, T2<:T1}(A::Vector{T1}, x::T2) = "success!"

julia> f!(Any[1,2,3], 4.)
"success!"

julia> f!(Integer[1,2,3], 4.)
ERROR: MethodError: no method matching f!(::Array{Integer,1}, ::Float64)
Closest candidates are:
  f!{T1,T2<:T1}(::Array{T1,1}, ::T2<:T1) at REPL[1]:1

julia> f!([1.,2.,3.], 4.)
"success!"