Consider the following scenario:
do_something( v :: Vector{ T } ) where { T <: Integer } = println( "v = $v" )
function d_dep( d :: Integer )
axis_iters = fill( -1:1, d )
for ig in Iterators.product( axis_iters ... )
g = [ ig ... ]
do_something( g )
end
end
This is a model of what I am doing in another code, and does what I want if I provide a positive integer argument to d_dep
:
julia> include( "nd.jl" )
d_dep (generic function with 1 method)
julia> d_dep( 1 )
v = [-1]
v = [0]
v = [1]
julia> d_dep( 2 )
v = [-1, -1]
v = [0, -1]
v = [1, -1]
v = [-1, 0]
v = [0, 0]
v = [1, 0]
v = [-1, 1]
v = [0, 1]
v = [1, 1]
julia>
Unfortunately d=0 means something in this case, and that's where things go pear-shaped:
julia> d_dep(0)
ERROR: MethodError: no method matching do_something(::Vector{Any})
Closest candidates are:
do_something(::Vector{T}) where T<:Integer
@ Main ~/julia/test/nd.jl:1
Stacktrace:
[1] d_dep(d::Int64)
@ Main ~/julia/test/nd.jl:9
[2] top-level scope
@ REPL[4]:1
julia>
The problem as I understand it is that when you apply the splat operator to the array axis_iters
it gets expanded to a empty list from which Iterators.Product
has nothing to get a type from, hence Vector{Any}
. So two questions:
Currently I have a if d == 0
in d_dep
. This is ugly. Is there a neater way to deal with the zero d case, or the whole structure in general? If it helps d should be positive semi-definite.
Actually I don't really understand why when d=0
the loop is executed at all. Why is it - is Julia like Fortran66 was alleged to be (but wasn't) in that it was mandated that all loops have at least 1 trip? [ In F66 zero trip loops were actually implementation defined ]
The result of Iterators.product()
is a zero-dimensional iterator. In Julia a zero-dimensional iterators (things like Array{Int, 0}
) are implemented to behave like a scalar
And you can see that you can iterate over a scalar:
julia> for i in 1
@show i
end
i = 1
So it should not be that surprising that you can iterate over a zero-dimensional Array
.
This is basically a language design decision, made (AFAIK) to make some broadcasting cases like [0, 1, 2] .+ 1
work consistently. You can read more here.
As for making the code work, I see two options:
Always make typeof(g) == Vector{Int}
, for example:
function d_dep(d::Integer)
axis_iters = fill(-1:1, d)
for ig in Iterators.product(axis_iters...)
g = Int[ig...]
do_something(g)
end
end
Or add another do_something
method to catch the d==0
case:
julia> do_something(v::Vector) = println("v = $v")
julia> d_dep(0)
v = Any[]