(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 :slight_smile:

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 :wink:)