# Detect argument is an iterable or a "scalar"

I keep finding myself wanting to branch on whether an argument is a collection (iterable?) or a scalar (single value). What’s the idiomatic way?

A toy example would be

``````function func(xs)
if is_iterable(xs) # <- How to do this?
for (i,x) in pairs(xs)
print("working on the \$(i)th item . . . "); flush(stdout)
dosomethingon(x)
end
println()
else
dosomethingon(xs)
end
end
``````

So far, the best I can think of is this inelegant solution

``````function func(xs)
if length(xs) > 1
# iterable
else
# scalar
end
end
func(3:10) # iterable
func([3]) # one-element array =~ scalar
``````

you can’t and you shouldn’t.

Maybe this a XY problem? can you explain why do you need to do this with the SAME method?

normal pattern in Julia is probably something like:

``````dosomething(x::Number) = ...

function dosomething(xs)
for (i,x) in pairs(xs)
dosomething(x)
end
end
``````
2 Likes

It is not do-able. The inability however shows the right thing: what if someone treats what that you treat as scalars as iterables? Julia code is loaded incrementally: Sam loads package A and Tom loads package B later, what if A and B are supposed (by Sam and Tom) to keep different assumptions about what should be scalar/iterable.

Interestingly, Julia core may not distinguish scalars in the way you’re used to:

``````julia> x = 1
1

julia> for i in x
println(i)
end
1

``````

I’d suggest you define `isiterable` for your own “application domain”:

``````MyPackage.isiterable(::MyType) = true
MyPackage.isiterable(::Number) = false
MyPackage.isiterable(::AbstractArray) = true
``````
1 Like

You should describe what you’re trying to accomplish with more context for a complete answer. Often, when I’m looking for something like this what I really want is a recursive reduction of the input:

``````countnumbers(x) = sum(countnumbers,x) # recursive definition
countnumbers(x::Number) = 1 # base case
countnumbers(x::AbstractChar) = 0 # special case

countnumbers([[1,2,3],4,(5,6),"seven"]) == 6
``````

But depending on how many base/special cases you need to define, this may not be practical.

1 Like
``````using Plots
using StrFormat
function func(xs, . . . )
# . . . here comes a lot of preparation to plot figures . . .
if is_iterable(xs)
for (i, x) in pairs(xs)
p = plot_a_figure_with_x(x, . . . )
savefig(p, f"myfig-p\%3.3d(i).png") # -> myfig-p001.png, myfig-p002.png, . . .
end
else
p = plot_a_figure_with_x(xs, . . . )
savefig(p, "myfig.png")
end
end
func(x1:delx:x2, . . .) # iterable
func(x, . . .) # scalar
``````

This isn’t a contrived example. I’m just trying to write a program exactly like this.

can you explain why do you need to do this with the SAME method?

It’s just convenient because the two cases share a lot of code. If I were to use two functions instead of one, I would need to separate out the preparation code (see the above code) into another function and call it from the two functions. It would be absolutely doable, but it’d be unnecessary complexity for my particular case at hand. The following solution would be much simpler.

If what I asked for is impossible, I’d just add a Boolean flag to indicate whether the argument is iterable or not:

``````function func(; xss, . . . )
(xs, iterable) = xss
if iterable . . .
. . .
end
func(xss = (xs, true), . . . )
func(xss = (y, false), . . . )
``````

A simple problem, a simple solution.

I just (incorrectly) imagined that Julia’s type hierarchy had something that indicates “iterability” (just like all the `Real` types implement the larger-than operator whereas the `Complex{T}` types don’t).

maybe the isbits() function can do what you ask

I might try something like the following:

``````function savemyplots(xs::AbstractVector{<:Real}, . . .)
p = plot_a_figure_with_x(xs, . . . )
savefig(p, "myfig.png")
end

function savemyplots(xs_collection, . . .)
for (i, x) in pairs(xs)
p = plot_a_figure_with_x(x, . . . )
savefig(p, f"myfig-p\%3.3d(i).png") # -> myfig-p001.png, myfig-p002.png, . . .
end
end

function func(xs, . . . )
# . . . here comes a lot of preparation to plot figures . . .
savemyplots(xs)
end
``````

Replace `AbstractVector{<:Real}` with whatever input type you want to recognize as a single plot-able element. Everything else will be iterated as a collection of plots to make. If you want multiple specific types to be recognized as single-plot types, then you might need a helper function or trait (like the `isiterable` suggestion above) and you can that to forward to the single or multiple version of your plotting.

That said, it seems that handling this dispatch at the level of `func` might be better. I would imagine that the preparatory work also depends on whether you plan to produce one or many plots.

Your option of adding a flag is totally reasonable too. However, at that point I would consider whether you really need `func` to be the name for both the single and multiple cases. It may be less confusing to handle each under a differently-named function.

As others have said, we don’t have a universal trait (much less a branch in the type hierarchy) to determine the iterability of an object. Whether something should be iterated or treated as a scalar is context-dependent so we could never hope to answer the question accurately and decisively.

For example, should a `String` be treated as a monolithic object or as a ordered collection of characters? Should a vector be treated as a single object (perhaps representing a single point in N-dimensional space?) or as a collection of numbers? It makes no sense to sort the (x,y,z) coordinates of a point but it’s entirely reasonable to sort a list of prices.

3 Likes

I don’t think I have ever written a piece of code where I don’t ‘know’ in the code whether a particular object is iterable or not.

Just write separate methods for scalar and AbstractVector inputs, where the latter handles preprocessing separately.

Thank you for your analysis! It’s a very accurate analysis of my problem. There, I think this is the crux of the problem:

That said, it seems that handling this dispatch at the level of `func` might be better. I would imagine that the preparatory work also depends on whether you plan to produce one or many plots.

No, that’s not the case and that is the point! I wrote the original code for a single plot. Then I’m trying to extend it for multiple plots, just to loop over the values of `x`, changing the filename from `myfig.png` to `myfig-p001.png`, . . . That’s the only difference.

My problem is that simple and the solutions everybody proposes are overkill.

I’m not against your solutions in general. I would write such code as everybody proposes if the two cases are different enough. I’m not saying that branching on whether the value is scalar or not is a superior solution in general. In most cases, dispatch is the superior solution.

If I write two functions to handle each cases separately, I would have to pass a lot of arguments to them from my preparatory code. I don’t think that extra complexity is worth it for my problem at hand.

If my code further extends and if the two cases (collection vs scalar) become different enough, then two separate functions would start to make sense. I don’t think, however, that it makes sense to do so just to avoid branching on whether the value is a collection or a scalar.

we don’t have a universal trait (much less a branch in the type hierarchy) to determine the iterability of an object. Whether something should be iterated or treated as a scalar is context-dependent so we could never hope to answer the question accurately and decisively.

Okay.

For example, should a String be treated as a monolithic object or as a ordered collection of characters? Should a vector be treated as a single object (perhaps representing a single point in N-dimensional space?) or as a collection of numbers? It makes no sense to sort the
(x, y, z) coordinates of a point but it’s entirely reasonable to sort a list of prices.

You explain why we cannot determine, in the current Julia, whether a value can be considered iterable or not. But, in the preceding paragraph you mentioned “trait”, and that approach isn’t impossible in principle. For example, the language designer could declare `Vector` to be a subtype of `Iterable`. Then, any `Vector` would have to behave like an `Iterable` in a context where a `Iterable` is expected. The language designer could decide that `String` is not an `Iterable` and then a `String` would not work as an `Iterable` and you would have to write `for c in iter(a_string)` to extract each character. And so on and so forth.

But, I’m sure that the Julia designers had good reasons not to take this approach.

Two points:

• Are you using collections that are not `AbstractArray`s? Can’t you write your own `isiterable` function which just tests for `AbstractArray` and `Tuple` and `AbstractDict`? This was suggested by @thautwarm, but you haven’t addressed it, afaict.

• You can share code between functions or methods, like this

``````_myprepcode() =...

function savemyplots(xs::AbstractArray,...)
_myprepcode()
for (i, x) in pairs(xs)
...
end
end

function savemyplots(xs::AbstractArray,...)
_myprepcode()
...
end
``````

Just my two cents …

If that is your only use here and you are fine with the definition of iterable that Julia has, i.e.,

``````for x in [1,2,3]  # will iterate over elements
for x in 1          # will also iterate over single element
for x in :a         # will fail with a method error
``````

you can just wrap your iteration part of the code into a try catch block and run the single plot code when catching a method error.

Note that Julia does have several notions of iteration, i.e., `for` loops run over iterables whereas `broadcast` has its own definition of broadcastable. These notions do not always agree, e.g., `[identity(x) for x in "abc"]` vs `identity.("abc")`. Thus, depending on your exact requirements, defining your own trait might be necessary.

Using dispatch instead of branching is also common in OOP languages and very light weight in Julia. In your case, just define the functions within your larger function doing the preparation and call them right away. Then, you don’t need to pass any arguments as they have access to the surrounding scope.

As people have said, the idiomatic way is to use separate methods for your iterables versus your non-iterables. You shouldn’t be referring to non-iterables as scalars because some scalars, like `Number`s, are actually iterable, they just have no dimensions and 1 iteration.

However, this is actually possible to do because the iteration interface starts simply at `iterate(xs)`. To check whether a function can be called on arguments, you can use `applicable(iterate, xs)`. This is a bad idea as a condition because this is some runtime work you can’t optimize away, but it’s a bit better than `try-catch`ing every `MethodError`. Bear in mind that `iterate(xs)` isn’t the only call that runs when `xs` is iterated, so using this as an `isiterable` assumes that whoever wrote the first called method also wrote the rest.

No, it cannot.

``````julia> isbits(1)
true

julia> isbits((1, 2, 3))
true
``````

Why would you suggest that?

1 Like

I join the others saying that it is not a good idea performance-wise and also for the readability of your code. If this does not matter to you, there is however a way to achieve what you asked using the `Base.hasmethod` function to check if xs has the `iterate` method. Something like `hasmethod(iterate, typeof((typeof(xs),)))`

This will not work because number types like `Int` are iterable:

``````julia> 2[1]
2

julia> first(2)
2

julia> hasmethod(iterate, (Int,))
true
``````

So you will not detect a scalar this way.

i had tried the following tests and conjectured that the function could distinguish the cases the OP cares about.

``````julia> isbitstype(Int)
true
julia> isbitstype(Float64)
true
julia> isbitstype(Vector)
false
julia> isbitstype(Tuple)
false
``````

But evidently I don’t know how the type system works in julia

Tuples can be `isbits` or not, it depends if they make reference to only `isbits` types or not.

But more than that, some scalar values are not `isbits`: `isbits(big(1))`.

Just `Tuple` is not even a concrete type.

``````julia> isconcretetype(Tuple)
false
``````

If this remark means that isbitstype(T) can be true only for some concrete types, it might be useful to add it in the help of the function, together with some further examples of the type `isbitstype(Tuple{Int,Int}) == true`

``````help?> isbitstype
search: isbitstype

isbitstype(T)

Return true if type T is a "plain data" type, meaning it is immutable and contains no references to other values, only primitive
types and other isbitstype types. Typical examples are numeric types such as UInt8, Float64, and Complex{Float64}. This category
of types is significant since they are valid as type parameters, may not track isdefined / isassigned status, and have a defined
layout that is compatible with C.
``````
1 Like