Inference for tuple of callable objects

I have some objects that are callable, each performing a certain univariate transformation. They are type stable; heavily simplified MWE:

using Base.Test

struct One end
struct Two end

(::One)(x) = x+1
(::Two)(x) = x+2

@inferred (One())(1)            # OK
@inferred (Two())(1)            # OK

What I want to do is collect them to transform vectors. I am using tuples, so that I can specialize the type, however, the transformation itself is not type stable:

struct Many{T}
    t::T
end

m = Many((One(),Two()))
(m::Many)(x) = [t(x) for (t,x) in zip(m.t, x)]
@inferred m([1,2]) # get Array{Int, 1}, infers Array{_, 1}

How can I fix this?

zip is type-unstable over heterogeneous tuples. If you don’t mind putting x in a tuple too, then you can use the unregistered Unrolled.jl

julia> using Unrolled

julia> (m::Many)(x) = unrolled_map((t,x)->t(x), m.t, x)

julia> @inferred m((1,2))
(2, 4)

It appears that I don’t need it; map works fine for both tuples and vectors:

(m::Many)(x) = map((t,x)->t(x), m.t, x)

@inferred m([1,2])
@inferred m((1,2))
@inferred m([1.0,2.0])
@inferred m((1.0,2))
1 Like

Cool! IIRC map will bail out once your tuples get larger than ~15, so check if you need that.

See also GitHub - yuyichao/FunctionWrappers.jl.

Apparently Vector to Vector works fine even for a lot of elements (eg N=1000 below), but N=15 breaks type inference in

using Base.Test

struct One end
struct Two end

(::One)(x) = x+1
(::Two)(x) = x+2

struct Many{T}
    t::T
end

(m::Many)(x) = map((t,x)->t(x), m.t, x)

N = 15
m = Many(ntuple(i->isodd(i) ? One() : Two(), N))
@inferred m(ones(N))
@inferred m(tuple(ones(N)...))

Since ATM I need vector to vector, I will go with map.