Help with error: "N not defined" in method signature but it is

I understand this might not be idiomatic, but I would like to understand what’s going on here.

When compiling, the code below gives warning:

WARNING: method definition for test at {File:Line} declares type variable N but does not use it.

And when attempting to run it, it throws: ERROR: UndefVarError: N not defined

Could someone help me understand why the warning and error?

function test(x::Vector{T}, y)::NTuple{N,T} where {N,T}
    return (1,2,3)
end

Unlike T, N isn’t constrained by any type in the signature of your method, so it pretty much doesn’t exist: how can the assertion on the return type make sure you’re returning a NTuple{N, T} if N comes from nowhere?

@giordano 's answer is correct, but I didn’t understand it in the first reading. I think it’s clearer to mention that the compiler should be able to infer what type is T through the input x, but there is no input that clarifies what type should N be.

1 Like

idk, seems to work

julia> function test(x::Vector{T}, y) where T
           return (1,2,3)::NTuple{N,T} where N
       end
test (generic function with 1 method)

julia> test(Int[], ())
(1, 2, 3)

Okay, these seem to be the droids you’re looking for:

julia> function test(x::Vector{T}, y)::(NTuple{N,T} where N) where T
           return (1,2,3)
       end
test (generic function with 1 method)

julia> test(Int[], ())
(1, 2, 3)

Unlike the previous code, which simply performs type-assertion, this one does type-conversion.

julia> test(Float64[], ())
(1.0, 2.0, 3.0)
2 Likes

You’re missing that the N from the method signature doesn’t exist, as the warning is telling you.

Hm, I don’t know that the warning has the right choice of wording.

julia> foo() where N = N
WARNING: method definition for foo at REPL[1]:1 declares type variable N but does not use it.
foo (generic function with 1 method)

julia> foo()
ERROR: UndefVarError: `N` not defined

seems like it would be better if the warning said something along the lines of

Thank you all for the replies. The first response did answer why, but I realize I should have also asked “and what can I do about it?” I can’t just omit N, because NTuple requires something in that slot. I need a way to specify the type: “an NTuple of unknown length and elements of type T”. NTuple{_,T} doesn’t work. NTuple{T} doesn’t work.

It appears @uniment answered that with (NTuple{N,T} where N) which makes sense. I wasn’t familiar with using parentheses and multiple where clauses like that, so I’ll keep that in mind.

I don’t think there is any difference. If you look at the generated code, I think it always generates a call to convert when there is a return type specified.

function test2(::Type{T}, x)::T where T<:Number
	return 1
end
@code_lowered test2(Float64, 1)

CodeInfo(
1 ─ %1 = $(Expr(:static_parameter, 1))
│   %2 = Base.convert(%1, 1)
│   %3 = Core.typeassert(%2, %1)
└──      return %3
)

Observe:

julia> function bar(::Type{T}, x)::T where T; x end
       function baz(::Type{T}, x) where T; x::T end
baz (generic function with 1 method)

julia> bar(Float64, 1)
1.0

julia> baz(Float64, 1)
ERROR: TypeError: in typeassert, expected Float64, got a value of type Int64

What you’re looking for here is NTuple{<:Any,T}:

function test(x::Vector{T}, y)::NTuple{<:Any,T} where {T}
    return (1,2,3)
end
julia> test(Float64[], ())
(1.0, 2.0, 3.0)
2 Likes

The error here is coming from the x::T, not the return of the method. Change it to:
function baz(::Type{T}, x) where T; x end
and it won’t give an error anymore.

Although this works, this is (strictly speaking) an abuse of the type system: leveraging an undocumented implementation detail that <:Any currently simply doesn’t perform any typecheck on type parameters (after all, 3<:Any throws an error, so it’s nonsensicle that NTuple{3,Int}<:NTuple{<:Any,Int} returns true). As such, when this loophole eventually gets fixed, your code will break.

See this recent discussion for more.

It appears I mistook your intent then: I thought you intended to communicate that the element type of the function’s output tuple is constrained in accordance with the argument’s element type. If not, you could’ve always just written this:

function test(x::Vector, y)::NTuple
    return (1,2,3)
end
1 Like