Ok, magic clearly is a vague term.
What I mean is that there is nothing special about Val
(i.e., the compiler has no mention to it coded inside it, it is literally struct Val{x}; end
in the Base
) and, therefore, the way Val
solves the type-instability could be replicated by any structure you yourself created, it only needs to encode the tuple size in type space. (This is different, for example, of Array
that is hard/impossible to replicate without using Array
internally, and that is not implemented in Julia itself.)
Also, you do not need ntuple
to take a Val
, you could wrap ntuple
in a function that takes a Val
and unwrap the value back to a number before passing to ntuple
and this could also solve the type-unstability (or instead unwraping Val
, unwraping any alternative you use).
The only magic here, which I don’t like, is that ntuple
does not document that it may also take a Val
as second parameter (which as I said, is not strictly necessary to deal with type-instability), what is probably a implementation detail being leaked and that should not be relied on.
The source code that defines the Val
ntuple
is:
# inferrable ntuple (enough for bootstrapping)
ntuple(f, ::Val{0}) = ()
ntuple(f, ::Val{1}) = (@_inline_meta; (f(1),))
ntuple(f, ::Val{2}) = (@_inline_meta; (f(1), f(2)))
ntuple(f, ::Val{3}) = (@_inline_meta; (f(1), f(2), f(3)))
@inline function ntuple(f::F, ::Val{N}) where {F,N}
N::Int
(N >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", N)))
if @generated
quote
@nexprs $N i -> t_i = f(i)
@ncall $N tuple t
end
else
Tuple(f(i) for i = 1:N)
end
end
and the non-val ntuple
is:
function ntuple(f::F, n::Integer) where F
t = n == 0 ? () :
n == 1 ? (f(1),) :
n == 2 ? (f(1), f(2)) :
n == 3 ? (f(1), f(2), f(3)) :
n == 4 ? (f(1), f(2), f(3), f(4)) :
n == 5 ? (f(1), f(2), f(3), f(4), f(5)) :
n == 6 ? (f(1), f(2), f(3), f(4), f(5), f(6)) :
n == 7 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7)) :
n == 8 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8)) :
n == 9 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9)) :
n == 10 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9), f(10)) :
_ntuple(f, n)
return t
end