# Conversion and promotion

**URL:** <https://discourse.julialang.org/t/conversion-and-promotion/110217>\
**Category:** General Usage\
**Tags:** question\
**Created:** [February 14, 2024, 5:46pm UTC](https://discourse.julialang.org/t/conversion-and-promotion/110217 "2024-02-14T17:46:42Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![hros](https://avatars.discourse-cdn.com/v4/letter/h/97f17d/32.png) [@hros](https://discourse.julialang.org/u/hros)\
**Post date:** [February 14, 2024, 5:46pm UTC](https://discourse.julialang.org/t/conversion-and-promotion/110217/1 "2024-02-14T17:46:42Z")

</div>

While working on an exercise on [exercism](https://exercism.io), I found myself struggling with the issue of [conversion and promotion](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/).  
The exercise called for implementing a user-defined type for complex numbers. Defining the type and the mathematical methods was straightforward, but I when I implemented the conversion and promotion rules, I either had a missing conversion or an ambiguity.  
In [one of the published solutions](https://exercism.org/tracks/julia/exercises/complex-numbers/solutions/haveltom), the conversion/promotion is defined as follows:

```julia
struct ComplexNumber{T<:Real} <: Number 
    Re::T
    Im::T
end
const jm = ComplexNumber(0, 1)
# a bunch of math operations....
promote_rule(::Type{ComplexNumber{T}}, ::Type{ComplexNumber{S}}) where {T<:Number,S<:Number} = ComplexNumber{promote_type(T,S)}
convert(::Type{ComplexNumber{T}}, x::ComplexNumber{S}) where {T<:Number,S<:Number} = ComplexNumber(convert(T, x.Re), convert(T, x.Im))
ComplexNumber(x::Real, y::Real) = ComplexNumber(promote(x,y)...)

```

Please explain why this combination of promotion/conversion/construction methods is the (only/recommended?) way  
What are the general guidelines when integrating a new type with existing types?

---

<div class="post-metadata">

**Author:** ![JeffreySarnoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeffreysarnoff/32/1980_2.png) [@JeffreySarnoff](https://discourse.julialang.org/u/JeffreySarnoff)\
**Post date:** [February 14, 2024, 8:08pm UTC](https://discourse.julialang.org/t/conversion-and-promotion/110217/2 "2024-02-14T20:08:19Z")

</div>

\ ***my take on the struct**

Where the components of a struct all share the same type, and there is more than one type that may be used when constructing the struct, then

struct StructName{T\<:_supertype\_for\_components_}

Where the components of a struct all share the same type, and there is more than one type that may be used when constructing the struct, and the structured type is intended to behave (participate in the Julia ecosystem) as a member of a larger type, then

struct StructName{T\<:_supertype\_for\_components_} \<: _supertype\_for\_behavior_

note that supertypes must be abstract types

In Julia, `isabstracttype(Complex) == false`, so we cannot use `Complex` as the supertype for behavior. We use `supertype(Complex) == Number`. If there were no Complex type in Julia, knowing that the type of the components is some kind of Real, we would use `supertype(Real) == Number`. Either way

struct ComplexNumber{T\<:Real} \<: Number

It is best practice to name structs using titlecase, and to name the constituent fields (struct components) using lowercase. It is helpful to use fieldnames that are more self-explanatory than not.

```julia
struct ComplexNumber{T<:Real} <: Number
    real::T
    imag::T
end

```

Where all components are of the same type, and that type is parameterized, It is utilitarian to provide a constructor that does not require the explicit parameter.

```julia
struct ComplexNumber{T<:Real} <: Number
    real::T
    imag::T
end

ComplexNumber(real::T, imag::T) where {T<:Real} =
    ComplexNumber{T}(real, imag)

```

It makes sense to allow construction of a ComplexNumber from a Real value;  
this allows that and preceding construction.

```julia
struct ComplexNumber{T<:Real} <: Number
    real::T
    imag::T
end

ComplexNumber(real::T, imag::T=zero(T)) where {T<:Real} =
    ComplexNumber{T}(real, imag)

```

and it is nice to show it well

```julia
const ImaginaryPostfix = "im"

function Base.show(io::IO, x::ComplexNumber)
    sgn = signbit(x.imag) ? " - " : " + "
    str = string(x.real, sgn, abs(x.imag), ImaginaryPostfix)
    print(io, str)
end

```

---

<div class="post-metadata">

**Author:** ![JeffreySarnoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeffreysarnoff/32/1980_2.png) [@JeffreySarnoff](https://discourse.julialang.org/u/JeffreySarnoff)\
**Post date:** [February 14, 2024, 9:54pm UTC](https://discourse.julialang.org/t/conversion-and-promotion/110217/3 "2024-02-14T21:54:06Z")

</div>

\*_my take on the promotion_

from the docs:

> promote(xs…)  
> Convert all arguments to a common type …

> promote\_rule(type1, type2)  
> Specifies what type should be used by promote when given values of types type1 and type2. This function should not be called directly, but should have definitions added to it for new types …

`promote_type(type1, type2, ...)` exists:  
Calling `promote_type` can be quite helpful.  
Adding methods to it can create hard to find problems.  
Add methods to `promote_rule` instead where possible.

For a new type (here, the struct ComplexNumber{T}), `promote` does not yet know how to convert two values each with its own, distinct, component type to a common ComplexNumber type.

However, Julia knows rules about how to promote two Real types  
using `promote_rule. With that, so does `promote`know how to promote two Real values of distinct types. `promote\_rule`is type-oriented and`promote` is value-oriented.

note the way to define promotion for a parameterized type

```julia
function Base.promote_rule(::Type{C1}, ::Type{C2}) where {T1, T2, C1<:ComplexNumber{T1}, C2<:ComplexNumber{C2}}
    T = promote_type(T1, T2)
    ComplexNumber{T}
end

```

(the case where T1 == T2 is handled automatically)

we can use this to define a constructor that convert the ComplexNumber’s parameterization

```julia
ComplexNumber{T}(x::ComplexNumber) where {T} =
    ComplexNumber(T(x.real), T(x.imag))

```

We define complex addition for two arguments of the same type (both types parameters’ are the same)

```julia
function Base.:(+)(x::ComplexNumber{T}, y::ComplexNumber{T}) where {T}
    real = x.real + y.real
    imag = x.imag + y.imag
    ComplexNumber(real, imag)
end

```

now, automagically, we support addition with mixed type ComplexNumbers

```julia
# using the prior post and this one

julia> a = ComplexNumber{Float32}(1.5, 0.75)
julia> b = ComplexNumber{Float64}(0.5, 1.25)
julia> a + b
2.0 + 2.0im

```
