# Conversion, promotion, user API?

**URL:** <https://discourse.julialang.org/t/conversion-promotion-user-api/50629>\
**Category:** General Usage\
**Created:** [November 23, 2020, 1:34pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629 "2020-11-23T13:34:22Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [November 23, 2020, 1:34pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/1 "2020-11-23T13:34:22Z")

</div>

I am struggling to find the right design due to the shear amount of possibilities, so I need help 😉

Given a type `FooID`, which is a simple number with a bit of additional convenience attributes (I added that to to show that I cannot do a simple type alias to `Int32`):

```julia
struct FooID
    value::Int32
    q1::Int8
    q2::Int8
    q3::Int8

    function FooID(value)
        d = digits(abs(value), pad=3)
        return new(value, d[1], d[2], d[3])
    end
end

```

This type is now used as field type in

```julia
struct Bar
    fooid::FooID
end

```

Now I need to define a ton of functions which take either `FooID`, `Bar` or even a simple `Integer`. I tried different ways to provide a general API and this is the best solution I have found so far:

```julia
convert(::Type{FooID}, bar::Bar) = bar.fooid
convert(::Type{FooID}, x::Integer) = FooID(x)

function isvalid(x)
    x = convert(FooID, x)
    x.fooid.q1 == 1
end

function hasfuture(x)
    x = convert(FooID, x)
    x.fooid.q1 > 2
end

```

This works, but I have to repeat that `convert` line everywhere. I also tried `promote`, `promote_rule` and `Union` things, but I would like to keep the API clear and understandable and could not find a better way yet.  
The first idea was to define

```julia
hasfuture(x::FooID)

```

and then implement `prmote_rule` to do the automatic conversion when a user falls it with `Bar`, but I don’t like that since it does not clearly communicate that it also accepts a `Bar`.

Another approach is to define

```julia
hasfuture(x::Union{FooID, Bar, Integer})

```

but that also doesn’t feel right either.

I also thought about using a type hierarchy and introduce an `AbstractFooID` where everything derives from, but of course `Integer` will still be a special case…

Is there a more Julian approach to this?

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [November 23, 2020, 1:52pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/2 "2020-11-23T13:52:50Z")

</div>

I think the Julian approach is something like this:

```julia
"""
    hasfuture(x::Union{FooID, Bar, Integer})

docstring here
"""
function hasfuture end

function hasfuture(x::Integer)
    id = FooID(x)
    id.q1 > 2
end

hasfuture(x::Bar) = x.fooid.q1 > 2
hasfuture(x::FooID) = x.q1 > 2

```

In other words, the docstring defines the API, not the implementation. So in the docstring you can say that the signature is `hasfuture(x::Union{FooID, Bar, Integer})`, but you actually implement it by implementing three different methods.

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [November 23, 2020, 1:54pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/3 "2020-11-23T13:54:29Z")

</div>

Ah yeah, you are right.

In fact, I don’t need to duplicate the logic either, it’s just

```julia
hasfuture(x::Bar) = hasfuture(FooID(x))

```

and then provide the conversion functions.

Make sense, thanks! 🙂

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [November 23, 2020, 1:56pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/4 "2020-11-23T13:56:28Z")

</div>

Oh wait, but then I end up with a lot of

```julia
hasfuture(x::Bar) = hasfuture(FooID(x))
haswhatever(x::Bar) = haswhatever(FooID(x))
isvalid(x::Bar) = isvalid(FooID(x))

```

This is clearly a pattern which should be tackled by Julia easily but I don’t see the forest anymore 🙈

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [November 23, 2020, 2:03pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/5 "2020-11-23T14:03:21Z")

</div>

That looks fine to me. I think that’s just the way it is. It’s better than this:

```julia
function hasfuture(x)
    if x isa Integer
        FooID(x).q1 > 2
    elseif x isa FooID
        x.q1 > 2
    elseif x isa Bar
        x.fooid.q1 > 2
    else
        throw(ArgumentError("oops"))
    end
end  

```

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [November 23, 2020, 2:05pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/6 "2020-11-23T14:05:35Z")

</div>

Hm I am sure there is a better design since this is clearly a code (concept) repetition:

```julia
hasfuture(x::Bar) = hasfuture(FooID(x))
haswhatever(x::Bar) = haswhatever(FooID(x))

```

I hope that someone has a better idea, at least I think that’s the right track.

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [November 23, 2020, 2:31pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/7 "2020-11-23T14:31:06Z")

</div>

if you can indeed reduce repetition to this, then you can do a loop `eval`:  
[https://github.com/JuliaLang/julia/blob/5b7699217267cec91d5b2b3d1ce1d47fd53f6068/base/sort.jl#L323-L325](https://github.com/JuliaLang/julia/blob/5b7699217267cec91d5b2b3d1ce1d47fd53f6068/base/sort.jl#L323-L325)

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [November 23, 2020, 2:33pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/8 "2020-11-23T14:33:34Z")

</div>

Yes that does not look so awful, I guess. I also thought about using a macro…

For now I have something similar to:

```julia
convert(::Type{FooID}, bar::Bar) = bar.fooid
convert(::Type{FooID}, x::Integer) = FooID(x)

function isvalid(x::FooID)
    x = convert(FooID, x)
    x.fooid.q1 == 1
end
isvalid(x::Union{Bar, Integer}) = isvalid(convert(FooID, x))

function hasfuture(x)
    x = convert(FooID, x)
    x.fooid.q1 > 2
end
hasfuture(x::Union{Bar, Integer}) = hasfuture(convert(FooID, x))

```

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [November 23, 2020, 2:49pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/9 "2020-11-23T14:49:59Z")

</div>

Here’s a generic approach that should work:

```julia
struct FooID
    value::Int32
    q1::Int8
    q2::Int8
    q3::Int8

    function FooID(value)
        d = digits(abs(value), pad=3)
        return new(value, d[1], d[2], d[3])
    end
end

fooid(x::FooID) = x
fooid(x::Integer) = FooID(x)

struct Bar
    fooid::FooID
end

fooid(x::Bar) = x.fooid

hasfuture(x) = fooid(x).q1 > 2

```

Now you only have to define one version of `hasfuture`, `haswhatever`, `isvalid`, etc. 🙂

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [November 23, 2020, 2:51pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/10 "2020-11-23T14:51:41Z")

</div>

Yes, that was actually one of my first approaches and it is basically the same as using convert. I just miss the API verbosity 😕

EDIT: a bit more information: the functions are often very large, so writing `fooid(x)` everywhere is a bit noisy, so one would anyways do something like `fooid = fooid(x)` at the beginning of the function, which is technically the same as `fooid = convert(FooID, x)`.

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [November 23, 2020, 2:58pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/11 "2020-11-23T14:58:43Z")

</div>

I think the generic approach is the most Julian approach here. It’s like starting a function with `a = zero(x)` instead of `a = 0`.

Also, defining `convert` here is a little un-idiomatic. You normally only define `convert` if you need _implicit_ conversion to happen when, e.g., adding an element to an array. If you need to explicitly construct a new value, you normally call a constructor, not `convert`.

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [November 23, 2020, 3:19pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/12 "2020-11-23T15:19:17Z")

</div>

In fact, defining `convert(::Type{A}, b::B)` when you really want a constructor can be a little dangerous. Later on you might accidentally push a `B` into a `Vector{A}` without meaning to. Implicit conversion also happens inside default constructors, so that’s another area where you could potentially introduce a bug by defining a `convert` method.

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [November 23, 2020, 6:14pm UTC](https://discourse.julialang.org/t/conversion-promotion-user-api/50629/13 "2020-11-23T18:14:44Z")

</div>

Alright thanks!
