# Looping over different types with common behavior

**URL:** <https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068>\
**Category:** Performance\
**Created:** [June 29, 2018, 3:22pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068 "2018-06-29T15:22:53Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![IssamT](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/issamt/32/4561_2.png) [@IssamT](https://discourse.julialang.org/u/IssamT)\
**Post date:** [June 29, 2018, 3:22pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068/1 "2018-06-29T15:22:53Z")

</div>

I consider two methods presented in the example below. As expected, the second one is better since it uses only concrete types.

```julia
ABSTRACT COLLECTION
  34.190 ms (999490 allocations: 15.25 MiB)
CONCRETE COLLECTION
  1.307 ms (0 allocations: 0 bytes)

```

Is there any way to have a good performance with a more elegant code?

```julia
using BenchmarkTools

abstract type HasInt end
struct A <: HasInt
    a::Int
    b::Float64
end
struct B <: HasInt
    a::Int
    b::Float64
    c::Float64
end
struct C <: HasInt
    a::Int
    b::Float64
    c::Float64
    d::Float64
end

value(obj::A) = obj.a
value(obj::B) = obj.a + 1
value(obj::C) = obj.a + 2

function createabstractcollection()
    srand(77777);
    arr = Vector{HasInt}()
    for i in 1:1000000
        t = rand(1:3)
        if t == 1
            push!(arr, A(i, 7.0))
        elseif t == 2
            push!(arr, B(i, 7.0, 7.0))
        else
            push!(arr, C(i, 7.0, 7.0, 7.0))
        end
    end
    return arr    
end

function sumabstractcollection(arr)    
    s = 0
    for obj in arr
        s += value(obj)
    end
    return s    
end

struct concretecollection
    A_vector::Vector{A}
    B_vector::Vector{B}
    C_vector::Vector{C}
end
concretecollection() = concretecollection(Vector{A}(), Vector{B}(), Vector{C}())

function createconcretecollection()
    srand(77777);
    arr = concretecollection()
    for i in 1:1000000
        t = rand(1:3)
        if t == 1
            push!(arr.A_vector, A(i, 7.0))
        elseif t == 2
            push!(arr.B_vector, B(i, 7.0, 7.0))
        else
            push!(arr.C_vector, C(i, 7.0, 7.0, 7.0))
        end
    end
    return arr    
end

function sumconcretecollection(arr)    
    s = 0
    for obj in arr.A_vector
        s += value(obj)
    end
    for obj in arr.B_vector
        s += value(obj)
    end
    for obj in arr.C_vector
        s += value(obj)
    end
    return s
end

function test()
    println("ABSTRACT COLLECTION")
    absarr = createabstractcollection()   
    @btime sumabstractcollection($absarr)
    
    println("CONCRETE COLLECTION")
    concrarr = createconcretecollection()
    @btime sumconcretecollection($concrarr)
end

test()

```

---

<div class="post-metadata">

**Author:** ![favba](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/favba/32/2735_2.png) [@favba](https://discourse.julialang.org/u/favba)\
**Post date:** [June 29, 2018, 3:39pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068/2 "2018-06-29T15:39:27Z")

</div>

Maybe using a tuple of concrete vectors instead of `concretecollection` might make it more elegant. For example, Your `sumconcretecollection` could be rewritten as:

```julia
function createconcretecollection()
    srand(77777);
    arr = (Vector{A}(), Vector{B}(), Vector{C}())
    for i in 1:1000000
        t = rand(1:3)
        if t == 1
            push!(arr[1], A(i, 7.0))
        elseif t == 2
            push!(arr[2], B(i, 7.0, 7.0))
        else
            push!(arr[3], C(i, 7.0, 7.0, 7.0))
        end
    end
    return arr    
end

function sumconcretecollection(arr::Tuple)
    s = 0
    for el in arr, obj in el
        s += value(obj)
    end
    return s
end

```

Edit: Well, my code actually gives the worst speed 😅

---

<div class="post-metadata">

**Author:** ![danielmatz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielmatz/32/2285_2.png) [@danielmatz](https://discourse.julialang.org/u/danielmatz)\
**Post date:** [June 29, 2018, 3:51pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068/3 "2018-06-29T15:51:02Z")

</div>

Another option is to manually dispatch for a few types in the abstract case.

```julia
function sumabstractcollectiondispatch(arr)
    s = 0
    for obj in arr
        if isa(obj, A)
            s += value(obj::A)
        elseif isa(obj, B)
            s += value(obj::B)
        elseif isa(obj, C)
            s += value(obj::C)
        else
            s += value(obj)
        end
    end
    return s
end

```

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [June 29, 2018, 4:20pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068/4 "2018-06-29T16:20:24Z")

</div>

You can store a concretely-typed vector of [FunctionWrappers](https://github.com/yuyichao/FunctionWrappers.jl) instead (essentially the same as storing a function pointer in C). Or if you want a slightly higher-level abstraction, you can try out my (unregistered) [Interfaces](https://github.com/rdeits/Interfaces.jl) package (which just stores a struct containing function pointers for your methods of interest, similar to the way virtual methods in C++ would work).

---

<div class="post-metadata">

**Author:** ![tkoolen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkoolen/32/1603_2.png) [@tkoolen](https://discourse.julialang.org/u/tkoolen)\
**Post date:** [June 29, 2018, 6:13pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068/5 "2018-06-29T18:13:46Z")

</div>

You could also use [TypeSortedCollections.jl](https://github.com/tkoolen/TypeSortedCollections.jl) for this (I’m the author). It basically automates your `createconcretecollection`/`sumconcretecollection` approach:

```julia
julia> using TypeSortedCollections

julia> tsc = TypeSortedCollection(createabstractcollection());

julia> @btime mapreduce(value, +, 0, $tsc)
  1.204 ms (0 allocations: 0 bytes)
500001499788

```

---

<div class="post-metadata">

**Author:** ![fabiangans](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fabiangans/32/2624_2.png) [@fabiangans](https://discourse.julialang.org/u/fabiangans)\
**Post date:** [June 29, 2018, 8:29pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068/6 "2018-06-29T20:29:59Z")

</div>

I really wish there was something like Rust Enums with fields in Julia [Enums - Rust By Example](https://doc.rust-lang.org/beta/rust-by-example/custom_types/enum.html) together with some nice pattern-matching syntax to do handle these cases efficiently.

It is so far the single language feature that I found in other languages which I am missing now and then when using Julia.

---

<div class="post-metadata">

**Author:** ![tkoolen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkoolen/32/1603_2.png) [@tkoolen](https://discourse.julialang.org/u/tkoolen)\
**Post date:** [June 29, 2018, 9:37pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068/7 "2018-06-29T21:37:13Z")

</div>

> [@fabiangans](#):
>
> Rust Enums with fields

That does seem nice.

Do note that performance of the naive version is much better with Julia 0.7 because of optimizations for small unions of bitstypes:

```julia
ABSTRACT COLLECTION
  8.286 ms (0 allocations: 0 bytes)
CONCRETE COLLECTION
  1.038 ms (0 allocations: 0 bytes)

```

but you’re still giving up quite a bit in this case, probably due to simd.

Edit: TypeSortedCollections on Julia 9965e7cab1b599da00f493fc0481ef7eadd29a52 for good measure (currently broken on latest master due to [bug in deprecated.jl:mapreduce · Issue #27871 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/27871)):

```julia
julia> tsc = TypeSortedCollection(createabstractcollection());

julia> @btime mapreduce(value, +, 0, $tsc)
  1.047 ms (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

**Author:** ![IssamT](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/issamt/32/4561_2.png) [@IssamT](https://discourse.julialang.org/u/IssamT)\
**Post date:** [June 30, 2018, 2:40pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068/8 "2018-06-30T14:40:28Z")

</div>

Thank you all for your suggestions. @tkoolen, Indeed `TypeSortedCollections.jl` seems to be a very good solution if the array isn’t updated often. If I understood correctly, I need to build a new `TypeSortedCollection` each time it gets updated before I loop on it, which is time consuming.

---

<div class="post-metadata">

**Author:** ![tkoolen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkoolen/32/1603_2.png) [@tkoolen](https://discourse.julialang.org/u/tkoolen)\
**Post date:** [June 30, 2018, 4:06pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068/9 "2018-06-30T16:06:50Z")

</div>

Well, there are `push!` and `append!` methods to modify an existing `TypeSortedCollection`, but the concrete types that are accepted must be specified by the user or automatically determined from an input collection when the `TypeSortedCollection` is constructed. I.e., you could also do:

```julia
using TypeSortedCollections
tsc = TypeSortedCollection{Tuple{Vector{A}, Vector{B}, Vector{C}}}()
append!(tsc, createabstractcollection())
push!(tsc, A(14, 8.0))

```

If you want to add a value that has a new type, then you do need to construct a new `TypeSortedCollection` that also supports that type.

---

<div class="post-metadata">

**Author:** ![IssamT](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/issamt/32/4561_2.png) [@IssamT](https://discourse.julialang.org/u/IssamT)\
**Post date:** [June 30, 2018, 4:14pm UTC](https://discourse.julialang.org/t/looping-over-different-types-with-common-behavior/12068/10 "2018-06-30T16:14:28Z")

</div>

Thank you very much. I will now go and read more the details of the package to use it properly.
