Why isn't this type signature allowed?

Given that functions f, g, and h all work, why isn’t function i allowed?
Shouldn’t i work like an alias for g just like h does?

x = 1
y = 0.1
function f(x::Number, y::Number)
    return "This is function f."
end
function g(x::T1 where T1<:Number, y::T2 where T2<:Number)
    return "This is function g."
end
function h(x::Vector{<:Number}, y::Vector{<:Number}) # Call with h([x], [y])
    return "This is function h."
end
function i(x<:Number, y<:Number)
    return "This is function i."
end
ERROR: syntax: "x <: Number" is not a valid function argument name
1 Like

It’s unclear if you’re referring to the type of the argument, or if the type is the argument

1 Like

Is that different from h? I could have a vector of types.

A Vector{<:Number} is a vector of values, each of which is a Number. It’s not a vector of types

For me (even in reading) it is not clear for this whether you mean that x is a type that inherits from number or a variable of a type that inherits from number.

edit: To be more precise, you could pass x=Float64 in there, which is one way to read your definition, but since it is not clear what you mean (informally reading it for me it does not get clear at least, but the compiler also complains)

I do see two solutions with different goals. You probably meant

function i(x::T, y::S) where {T<:Number, S<:Number}
    return "This is function i."
end

that is x and y are some kind of numbers (informally said).
This is basically your g just that the scope of the T and S is a little larger, because you could also do

function i(x::T, y::T) where {T<:Number}
    return "This is function i."
end

which would say they are some kind of number, but the same.

Yup, this is the crux – x::T works when x are values, x<:T works for types x (of course any x can be both a value and type, but the operator gives context then). Function arguments are always treated as values, so you can’t <: them.

4 Likes

In that case, you can use

function h(x::Vector{Type{<:Number}}, y::Vector{Type{<:Number}})
    return "This is function h."
end

Interestingly, this will fail:

julia> h([Int], [Float32])
ERROR: MethodError: no method matching h(::Vector{DataType}, ::Vector{DataType})

because

julia> typeof([Int])
Vector{DataType}

You have to enforce the eltype:

julia> h(Type{Int}[Int], Type{Float32}[Float32])
"This is function h."

Similarly, i could be

function i(x::Type{<:Number}, y::Type{<:Number})
    return "This is function i."
end
2 Likes

I’m starting to get it.
f just doesn’t seem like it should work to me. x is not a Number it is an Int which is a subtype of Number. Thus I want to indicate that with x<:Number just like in h.

Also, DNF showed that you have to explicitly write Type whenever you want to operate on types themselves, so the type vs value argument seems clear regardless.

But x is a Number:

julia> 1 isa Number
true

That is how subtype relationships work. For any value of type T, that value is also belongs to the supertypes of T:

julia> isa.(1, (Int, Signed, Integer, Real, Number, Any))
(true, true, true, true, true, true)
1 Like

I see. So the Vector equivalent to f is j:

function j(x::Vector{Number}, y::Vector{Number})
    return "This if function j."
end
julia> j(Vector{Number}([x]), Vector{Number}([y]))
"This if function j."

Almost, but not quite. Due to type in variance, Vector{Int} is not a subtype of Vector{Number}. The equivalent is

function j(x::Vector{<:Number}, y::Vector{<:Number})
    return "This if function j."
end

Yes, I do know that fact and have it as function h. Although I guess I don’t fully understand since j works as I expected but k doesn’t:

struct MyType{T<:Number}
    x::T
end
function k(x::Vector{MyType{<:Number}})
    return "This is function k."
end
julia> k([MyType(x)])
ERROR: MethodError: no method matching k(::Vector{MyType{Int64}})
Closest candidates are:
  k(::Vector{MyType})

Try

function k(x::Vector{<:MyType{<:Number}})
1 Like

You can also write

function k(x::Vector{MyType{T}}) where {T<:Number}

I was a bit surprised, because I though that Vector{MyType{<:Number}} was the same as Vector{MyType{T}} where {T<:Number}. But it turns out to be a tiny bit different:

julia> Vector{MyType{<:Number}} === Vector{MyType{T}} where {T<:Number}
false

julia> Vector{MyType{<:Number}} === Vector{MyType{T} where {T<:Number}}
true

The difference is the location of the closing }s.

1 Like

Yes, but why?
What is the full identical equivalence for this form?

I don’t see how that would make a functional difference. The where defines T the same either way, and the where location doesn’t matter in this case from the documentation:

A correct way to define a method that accepts all arguments of type Point{T} where T is a subtype of Real is:

function norm(p::Point{<:Real})
    sqrt(p.x^2 + p.y^2)
end

(Equivalently, one could define function norm(p::Point{T} where T<:Real) or function norm(p::Point{T}) where T<:Real; see UnionAll Types.)

Or, more semantically, whether the vector has to contain elements of the same MyType which all are constrained to the same T<:Number, or whether the vector can contain different MyType, possibly with different T (each of which is still constrained to be <:Number).

1 Like

The case you quote from the docs is not the same, because that only has one level of type parameters.

Ah, thanks. That makes sense.

The shorthand version from Gunnar doesn’t equal the longhand version from DNF though. What does it expand to?

julia> Vector{<:MyType{<:Number}} === Vector{MyType{T}} where {T<:Number}
false

Also, how can these all be true?

julia> isconcretetype(Vector{MyType})
true

julia> isconcretetype(Vector{MyType{Int}})
true

julia> isconcretetype(Vector{MyType{<:Number}})
true

(This one is false: isconcretetype(Vector{<:MyType{<:Number}}).)

At least we have

julia> (Vector{MyType{T}} where {T<:Number}) <: Vector{<:MyType{<:Number}}
true
1 Like