# (Idiomatic) functions on vectors containing elements of various types

Hello everyone,

I am fairly new to Julia. I love it so far, but I struggle a bit with the type system right now.

I have a collection of concrete types A,B,C, D … that implements an abstract type Z.
I would like to construct functions that operate only on containers of a subset of these types, say A, B and C, such as

``````bar([A(), B(), C(), A(), A(), B()])
``````

My naive guess was to define

``````Foo = Union{A,B,C}
``````

and

``````function bar(v::Vector{T}) where T<:Foo
for x in v
println(x)
end
end
``````

but it does not work:

``````x = A()
y = B()

bar([x,y]) # ERROR: MethodError: no method matching bar(::Vector{Any})
``````

It is easy to reproduce by defining `Foo = Union{Int,String}`.

My understanding from the error and my experiments is that the above definition work only when `x` and `y` are of the same subtype of `Foo`, either all `A`, or all `B` or all `C`.

Hence my question: how to define a function on a vector containing elements of various types?
(Or using VarArg, or other “container” types)
What is the right, idiomatic pattern? Which types worth to be aliased?

Thank you

I think you may have some lingering state in your code - `bar(x,y)` will never call `bar(v::Vector{T}) where T<:Foo` because the latter expects only a single argument - a `Vector`, not two arguments.

If you got that `MethodError` from `bar([A(), B(), C(), A(), A(), B()])`, it’s possible that you got a `Vector{Any}` out of this, since julia has to find a common type for all of these, which may end up as `Any`.

Do you have an runnable, standalone example?

I suppose you meant `bar([x,y])`, otherwise you are not passing a vector.

The point is, that in the absence of a rule, `A` and `B` promote to `Any`. Julia doesn’t promote to Union types by default:

``````julia> promote_type(String, Int)
Any
``````

It is easy to define an appropriate rule though:

``````julia> import Base: promote_rule # import to overload

julia> struct A end; struct B end; struct C end;

julia> Foo = Union{A,B,C}
Union{A, B, C}

julia> promote_rule(::Type{S}, ::Type{T}) where {S<:Foo, T<:Foo} = Foo
promote_rule (generic function with 125 methods)

julia> [A(),B(),C()]
3-element Vector{Union{A, B, C}}:
A()
B()
C()
``````

Now the function `bar` dispatches correctly

``````julia> bar([A(),B()])
A()
B()

julia> bar([A(),B(),"Spoiled!"])
ERROR: MethodError: no method matching bar(::Vector{Any})
Closest candidates are:
bar(::Vector{T}) where T<:Union{A, B, C} at REPL[6]:1
Stacktrace:
[1] top-level scope
@ REPL[9]:1
``````
2 Likes

Sorry I forgot the brackets I meant `bar([x,y])`.

Here is a MWE

``````Foo = Union{Int,String}

function bar(v::Vector{T}) where {T<:Foo}
for x in v
println(x)
end
end

bar([1, 2]) # fine
bar(["a", "b"]) # fine
bar([1, "a"]) # ERROR: MethodError: no method matching bar(::Vector{Any})
``````

Thank you for your detailed explanation of promotion, I was not aware of this concept, but it makes lot of sense that there is actually no good supertype fallback unless you specify it.

Is there another way to implement this pattern that does not rely on promotion?
For example, is it possible to define an abstract type `S` that has only `A`, `B` and `C` for subtypes, and write function against this type?

(In my use case `A, `B`, `C`, … come from an external library.)

``````julia> [1, "a"] |> typeof
Vector{Any} (alias for Array{Any, 1})
``````

The most common supertype for `Int` and `String` is `Any` - julia only very rarely builds unions voluntarily. You can force the element type of the vector to be of a specific type though:

``````julia> const Foo = Union{Int,String}
Union{Int64, String}

julia> Foo[1, "a"] |> typeof
Vector{Union{Int64, String}} (alias for Array{Union{Int64, String}, 1})
``````

Also, for type aliases like `Foo`, make sure they’re `const`.

In the example you’ve given, the objects don’t have a common type, but in your OP you mentioned that they do - I’d suggest typing the container with that common type, which will allow the function you wrote to work. An alternative would be to write it like

``````function bar(v::Vector{<:Foo})
# implementation
end
``````

since you don’t actually need to access `T` and you allow your container to be of mixed type, not a single concrete type that every object must have.

2 Likes

Thank you for you clear anwsers. The `T[x,y]` syntax is a nice addition to my Julia knowledge!

This totally works, but the point is to restrict the scope of the `bar` so that it accepts only few of the subtypes of `Foo` as elements of the vector `v`.

By looking at the ex’s presented here, i tried as below to restrict the vector only to contain `subtypes of Z`

``````abstract type Z end

struct A <: Z end
struct B <: Z end
struct C <: Z end

const Foo = Union{A, B, C}

function bar(v::Vector{<:Foo})
for x in v
println(x)
end
end

bar(Foo[A(),B(),C()]) # fine
bar(Foo[1, 2]) # Error
bar(Foo["a", "b"]) # Error
bar(Foo[1, "a"]) # Error
``````

Is this what is expected ?

P.S : Please note that I’m a beginner too, so i could be completely wrong (just wanted to try bcz this question was interesting to me )