# Best practice to handle type annotations

**URL:** <https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925>\
**Category:** General Usage\
**Created:** [March 13, 2025, 4:23pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925 "2025-03-13T16:23:42Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![dnldlg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnldlg/32/209465_2.png) [@dnldlg](https://discourse.julialang.org/u/dnldlg)\
**Post date:** [March 13, 2025, 4:23pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/1 "2025-03-13T16:23:42Z")

</div>

From reading the [Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips) in the Julia manual, I grasped that type declarations generally improve the performance and abstract types should be avoided. So lets say I have a function

```julia
function func(x, n::Integer)
    y = Real[]
    for i = 1:n
        push!(y, x^i)
    end
    return y
end

```

that I want to improve by replacing all abstract types:

```julia
function func(x, n::Integer)
    y = Float64[]
    for i = 1:n
        push!(y, x^i)
    end
    return y
end

```

Now I can imagine cases, where the specific type `Float64` might be non-ideal, e.g. when the code is executed on a GPU that likes to work with 32-bit input. In that case, I would want to replace every hard-coded `Float64` wtih `Float32` etc. How can I avoid doing the replacement depending on my use case. Would declaring my own type somewhere in the package useful, so that I need to replace it only once?

```julia
const MyFloat = Float64

# [...]

function func(x, n::Integer)
    y = MyFloat[]
    for i = 1:n
        push!(y, x^i)
    end
    return y
end

```

I wolud like to hear your ideas for such a situation.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [March 13, 2025, 4:32pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/2 "2025-03-13T16:32:15Z")

</div>

> [@dnldlg](#):
>
> I grasped that type declarations generally improve the performance and abstract types should be avoided.

Read the performance tips again — that’s not what they say.

In particular, abstract types declarations for _function arguments_ do **not** hurt performance. See [argument-type declarations in the manual](https://docs.julialang.org/en/v1/manual/functions/#Argument-type-declarations). Similarly for declaring return types or local-variable types.

> [@dnldlg](#):
>
> `function func(x::Float64, y::Int64)::Float64`

This has no effect on performance compared to `func(x::Real, y::Integer)`. It just makes your function less generic.

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [March 13, 2025, 4:50pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/3 "2025-03-13T16:50:11Z")

</div>

The equally fast, but generic version of your function would look like this:

```julia
function func(x::T, n::Integer) where {T<:Real}
    y = T[]
    for i = 1:n
        push!(y, x^i)
    end
    return y
end

```

---

<div class="post-metadata">

**Author:** ![dnldlg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnldlg/32/209465_2.png) [@dnldlg](https://discourse.julialang.org/u/dnldlg)\
**Post date:** [March 13, 2025, 4:53pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/4 "2025-03-13T16:53:54Z")

</div>

Thank you, I misunderstood this aspect in the manual. I changed the example in the question.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [March 13, 2025, 5:03pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/5 "2025-03-13T17:03:14Z")

</div>

> [@gdalle](#):
>
> ```julia
> function func(x::T, n::Integer) where {T<:Real}
> y = T[]
> 
> ```

You could also just use `y = typeof(x)[]` or `y = Vector{typeof(x}}(undef, 0)`. You don’t need to explicitly declare type parameters. This is also more flexible because you can compute types, e.g. `typeof(float(x)^2)` or calls to functions like `promote_type`.

The key idea in generic programming is to infer (either implicitly or explicitly) the types in your function from the types of the arguments.

---

<div class="post-metadata">

**Author:** ![dnldlg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnldlg/32/209465_2.png) [@dnldlg](https://discourse.julialang.org/u/dnldlg)\
**Post date:** [March 13, 2025, 5:34pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/6 "2025-03-13T17:34:56Z")

</div>

But what if there is no variable to infer the type from, like in this example:

```julia
function func(n::Integer)
    y = Complex{typeof(??)}[] # replace ?? with the correct type
    for k = 1:n
        push!(y, exp(im * pi / k))
    end
    return y
end

```

---

<div class="post-metadata">

**Author:** ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)\
**Post date:** [March 13, 2025, 5:45pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/7 "2025-03-13T17:45:37Z")

</div>

Then you’ll need to make provisions to specify it manually. Something like

```julia-repl
julia> function func(::Type{T}, n::Integer) where T
           y = complex(T)[]
           for k = 1:n
               push!(y, exp(im * pi / k))
               # EDIT: see comments below regarding doing the calculation with the target type
           end
           return y
       end
func (generic function with 1 method)

julia> func(Float16, 8)
8-element Vector{ComplexF16}:
  Float16(-1.0) + Float16(0.0)im
   Float16(0.0) + Float16(1.0)im
   Float16(0.5) + Float16(0.866)im
 Float16(0.707) + Float16(0.707)im
 Float16(0.809) + Float16(0.588)im
 Float16(0.866) + Float16(0.5)im
 Float16(0.901) + Float16(0.4338)im
 Float16(0.924) + Float16(0.3826)im

```

(You could alternatively use `function func(T::Type, n::Integer)` for the definition, but there can sometimes be performance differences between these for complicated reasons including but not limited to [this](https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing).)

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [March 13, 2025, 5:52pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/8 "2025-03-13T17:52:01Z")

</div>

> [@dnldlg](#):
>
> ```julia
> function func(n::Integer)
> y = Complex{typeof(??)}[] # replace ?? with the correct type
> for k = 1:n
> push!(y, exp(im * pi / k))
> end
> return y
> end
> 
> ```

Here, it sounds like you want to infer the type from the type of `exp(im * pi / k)` (which is more efficiently computed as `cis(pi/k)`, by the way). One way would be:

```julia
y = Complex{typeof(cis(pi/one(n)))}[]

```

Another way would be to use `map`, which works out the type for you:

```julia
y = map(k -> cis(pi/k), 1:n)

```

or broadcasting:

```julia
y = cis.(pi ./ (1:n))

```

or a comprehension as noted by @DNF below.

Or you could specify a precision via an argument as noted by @mikmoore.

> [@mikmoore](#):
>
> ```julia
> function func(::Type{T}, n::Integer) where T
> y = complex(T)[]
> for k = 1:n
> push!(y, exp(im * pi / k))
> 
> ```

Note that this may not do what you want, because it is not using `T` for the computation. You’d probably want:

```julia
push!(y, exp(im * T(pi) / k))

```

or similar. e.g. this would allow you to call `func(BigFloat, n)` and have the computation be done to `BigFloat` accuracy.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [March 13, 2025, 5:55pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/9 "2025-03-13T17:55:14Z")

</div>

[qote=“dnldlg, post:6, topic:126925”]  
`push!(y, exp(im * pi / k))`  
[/quote]

Comprehension are _really_ nice for this, particularly for simple expressions:

```julia
y = [cis(pi/k) for k in 1:n] 

```

(i used `cis` instead of exp(im) since it’s faster.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [March 13, 2025, 6:01pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/10 "2025-03-13T18:01:14Z")

</div>

> [@dnldlg](#):
>
> ```julia
> for i = 1:n
> push!(y, x^i)
> end
> 
> ```

BTW, a side remark. For performance you normally want avoid operations that get gradually more expensive, which `x^i` does as `i` increases (even if it only increases logarithmically). Instead, you can do

```julia
xi = x
for i in 1:n
    xi *= x
    push!(y, xi)
end

```

---

<div class="post-metadata">

**Author:** ![dnldlg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnldlg/32/209465_2.png) [@dnldlg](https://discourse.julialang.org/u/dnldlg)\
**Post date:** [March 13, 2025, 6:16pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/11 "2025-03-13T18:16:01Z")

</div>

Very insightful, thanks. Seems like one can infer more than I thought 😄

But let us assume that I encounter a situation where I **absolutely cannot** infer the precision, hence in such a situation I would go for @mikmoore’s suggestion. Now suppose that there is a large simulation where this precision has to be set many times. Would I use a solution like `MyFloat` in my original question, which I set in the beginning and use everywhere, or is there a magic command where the precision can be set globally?

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [March 13, 2025, 6:17pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/12 "2025-03-13T18:17:50Z")

</div>

> [@dnldlg](#):
>
> Now suppose that there is a large simulation where this precision has to be set many times

Why would have to be set “many times” as opposed to being passed to one entrypoint function and propagating from there to the rest of the computation?

---

<div class="post-metadata">

**Author:** ![dnldlg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnldlg/32/209465_2.png) [@dnldlg](https://discourse.julialang.org/u/dnldlg)\
**Post date:** [March 13, 2025, 6:26pm UTC](https://discourse.julialang.org/t/best-practice-to-handle-type-annotations/126925/13 "2025-03-13T18:26:55Z")

</div>

Because of my sloppy programming style, I suppose 😬 Propagation it is a very good idea. Thank you all, you helped me a lot!
