Let’s say I want to define a function `tmap`

which applies a given function to several arguments, where – this is the tricky part – the arguments are `DataType`

s. My first try was the “obvious”

```
tmap1(f, xs...) = map(f, xs)
```

which works

```
julia> tmap1(zero, Int, Float64, UInt8)
(0, 0.0, 0x00)
```

but the return-type can not be inferred:

```
julia> @code_warntype tmap1(zero, Int, Float64, UInt8)
Variables:
#self#::#tmap1
f::Base.#zero
xs::Tuple{DataType,DataType,DataType}
Body:
begin
return (Core.tuple)((f::Base.#zero)((Base.getfield)(xs::Tuple{DataType,DataType,DataType}, 1)::DataType)::Any, (f::Base.#zero)((Base.getfield)(xs::Tuple{DataType,DataType,DataType}, 2)::DataType)::Any, (f::Base.#zero)((Base.getfield)(xs::Tuple{DataType,DataType,DataType}, 3)::DataType)::Any)::Tuple{Any,Any,Any}
end::Tuple{Any,Any,Any}
```

So I tried the “recursion trick”, as follows:

```
tmap2(f, x, xs...) = (f(x), tmap2(f,xs...)...)
tmap2(f) = ()
```

which works only slight better:

```
julia> @code_warntype tmap2(zero, Int, Float64, UInt8)
Variables:
#self#::#tmap2
f::Base.#zero
x::Any
xs::Tuple{DataType,DataType}
Body:
begin
return (Core.tuple)(0, ($(QuoteNode(zero)))((Core.getfield)(xs::Tuple{DataType,DataType}, 1)::DataType)::Any, ($(QuoteNode(zero)))((Core.getfield)(xs::Tuple{DataType,DataType}, 2)::DataType)::Any)::Tuple{Int64,Any,Any}
end::Tuple{Int64,Any,Any}
```

i.e. return-type inference is still incomplete. In my understanding the inference fails because `typeof(Int)==DataType`

instead of `typeof(Int)==Type{Int}`

so that the `xs`

in `tmap1`

and `tmap2`

becomes a `Tuple{DataType,...}`

, losing the type information. (Correct?)

Now I have come up with a solution using generated functions, as follows (I can’t seem to get the quoting right, so I turned to `Expr`

):

```
tget{T}(t::Type{Type{T}}) = T # helper function
@generated function tmap3(f, xs...)
args = Any[Expr(:call, :f, tget(x)) for x in xs]
Expr(:tuple, args...)
end
```

for which return-type inference succeeds:

```
@code_warntype tmap3(zero, Int, Float64, UInt8)
Variables:
#self#::#tmap3
f::Base.#zero
xs::Any
Body:
begin # line 1:
return (Core.tuple)(0, (Base.sitofp)(Float64, 0)::Float64, (Base.checked_trunc_uint)(UInt8, 0)::UInt8)::Tuple{Int64,Float64,UInt8}
end::Tuple{Int64,Float64,UInt8}
```

(I believe the `xs::Any`

is harmless.)

This works on 0.5 and 0.6, but I wonder if this is a correct and future-proof approach. As far as I can figure out, it happens to work because during the generation stage of the generated function (in which the arguments are replaced by their types), the argument types are actually `Type{Int}`

etc. despite `typeof(Int)==DataType`

. Proof:

```
julia> @generated function test(xs...); dump(xs); nothing; end
test (generic function with 1 method)
julia> test(Int, Float64, UInt8)
Tuple{DataType,DataType,DataType}
1: Type{Int64} <: Any
2: Type{Float64} <: Any
3: Type{UInt8} <: Any
```

My question is: Is this behaviour something I can rely on, or is this an (accidental?) implementation detail? (Or alternatively, is there a simpler type-stable way of writing `tmap`

?)