# Parametric type conversion without eval/repetition?

**URL:** <https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308>\
**Category:** General Usage\
**Tags:** parametric-types, convert\
**Created:** [January 31, 2021, 1:38am UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308 "2021-01-31T01:38:41Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![evanfields](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/evanfields/32/1744_2.png) [@evanfields](https://discourse.julialang.org/u/evanfields)\
**Post date:** [January 31, 2021, 1:38am UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/1 "2021-01-31T01:38:41Z")

</div>

Suppose I have several parametric wrapper types, like so (toy example):

```julia
abstract type AbsWrapper end
struct WrapperA{T <: Real} <: AbsWrapper
    x::T
end
struct WrapperB{T <: Real} <: AbsWrapper
    x::T
end

```

I’d like to write converter functions that convert instances of an `AbsWrapper` to a “similarly typed” value wrapping a `Float64`. E.g.  
`to_float64_form(w::WrapperA) = WrapperA(Float64(w.x))`

Great, this works! And if I have just two wrapper types and one conversion, two one-line functions are all I need. But suppose I have types `WrapperA`, `WrapperB`, `WrapperC`, etc., and I want to write converters `to_float64_form`, `to_float32_form`, `to_rational_form`,… Then there are (number of types) \* (number of conversion) functions to write, and the source code gets repetitive. This can be solved (or at least mitigated?) with `@eval`, e.g.

```julia
for T in [WrapperA, WrapperB]
    @eval to_float64_form(val::$T) = $T(Float64(val.x))
end

```

But I’m not sure if `@eval` is necessary here. This feels like a use case that should be solvable with a single parametric method, but I’m struggling. Is `@eval` the preferred way to handle this, and if not what is best practice?

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [January 31, 2021, 2:00am UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/2 "2021-01-31T02:00:13Z")

</div>

Your `@eval` based approach looks good assuming the best design is creating many variants of basically the same type. However, I’m not sure this is the case. Instead you might an enum that stores a tag to associate with the `Wrapper`. Which approach is better depends a bit on what your eventual use-case is.

---

<div class="post-metadata">

**Author:** ![marius311](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marius311/32/3953_2.png) [@marius311](https://discourse.julialang.org/u/marius311)\
**Post date:** [January 31, 2021, 2:48am UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/3 "2021-01-31T02:48:39Z")

</div>

Depending what other type-parameters / constructors your real case has, you could use `Base.typename`, which strips out all type parameters like `Base.typename(WrapperA{Float32}).wrapper` will return just `WrapperA`. Then you can just re-call the constructor with the appropriate conversion:

```julia
to_float64_form(val::A) where {A<:AbsWrapper} = Base.typename(A).wrapper{Float64}(val.x)

# returns WrapperA{Float64}(1.0), is type-stable:
to_float64_form(WrapperA(1f0)) 
# same for:
to_float64_form(WrapperB(1f0)) 

```

---

<div class="post-metadata">

**Author:** ![evanfields](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/evanfields/32/1744_2.png) [@evanfields](https://discourse.julialang.org/u/evanfields)\
**Post date:** [January 31, 2021, 3:12am UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/4 "2021-01-31T03:12:51Z")

</div>

@Oscar_Smith and @marius311, thank you both for the helpful responses.

@marius311 I can’t find where I saw this, so I’m starting to doubt myself, but I thought I recalled that using `.wrapper` isn’t good practice, since the structure of `Base.typename(A)` is an implementation detail subject to change. Not so? I guess I’m even worried about using `typename`, since it’s not documented. If these are both okay, then yeah this seems like a nice concise solution!

@Oscar_Smith A little detail on my use case for context. I’m working on a simple hexagon library. There are several kinds of [hexagon coordinates](https://www.redblobgames.com/grids/hexagons/#coordinates). I have (for now) hexagon types `HexFlatTop` and `HexPointyTop`, both `<: AbstractHexagon`. Both hex types are wrappers around a coordinate object (and possibly other information like size). So the conversions I’m interested in look like `HexFlatTop{CoordCubic}` → `HexFlatTop{CoordAxial}`, etc. With `@eval` I have

```julia
for hextype in (HexPointyTop, HexFlatTop), coordtype in (CoordAxial, CoordCubic)
    @eval to_coord_form(::Type{$coordtype}, h::$hextype) = ($hextype)(convert($coordtype, h.coords))
end

```

---

<div class="post-metadata">

**Author:** ![marius311](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marius311/32/3953_2.png) [@marius311](https://discourse.julialang.org/u/marius311)\
**Post date:** [January 31, 2021, 3:22am UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/5 "2021-01-31T03:22:06Z")

</div>

> [@evanfields](#):
>
> `Base.typename(A)` is an implementation detail subject to change.

Yea I think that’s right, if you have low tolerance for possibly needing to update this code with future Julia versions, I think @Oscar_Smith’s suggestion is right, make the `A` or `B` be a type parameter rather than part of the name, then you can use normal dispatch. You always type alias it back to exactly what you have,

```julia
struct A end
struct B end
struct GenericWrapper{AorB, T} <: AbsWrapper
    x :: T
end
const WrapperA{T} = GenericWrapper{A,T}
const WrapperB{T} = GenericWrapper{B,T}

```

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [January 31, 2021, 3:39am UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/6 "2021-01-31T03:39:24Z")

</div>

In this case, one option to consider would be to have 1 struct with multiple constuctors. That way your program will be working on data of all the same type, but users can input how they want.

---

<div class="post-metadata">

**Author:** ![evanfields](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/evanfields/32/1744_2.png) [@evanfields](https://discourse.julialang.org/u/evanfields)\
**Post date:** [February 8, 2021, 3:16am UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/7 "2021-02-08T03:16:19Z")

</div>

Sorry to necro a week-old thread, but: I keep thinking about this problem, and realize I don’t understand something about the design of Julia’s type system. Why is there no function to get the “outer” type of a parametric type, ie the non-parametric part of a parametric type. For example, `Set{Int} -> Set`.

As @marius311 shows above, this is easy enough to write: `outer_type(t) = Base.typename(t).wrapper`. But, that implementation requires undocumented internals and an undocumented and un-exported `Base` function. So it doesn’t feel like the intended way to do this, and best I can tell by reading the docs and liberal `apropos()`, there isn’t a documented built-in for this functionality.

Why not? Is getting the outer type of a type not a useful thing to do? It would certainly help in the case presented in this thread, though I think Oscar and Marius are correct that my initial post is mostly just bad design. But in general having a function that maps like `Set{Int} -> Set` or `Vector{String} -> Array` seems…pretty handy? It’d be a way to learn about the flavor of a collection, or the structure of an object without regard to what kind of data that object holds.

I’ve been using Julia and slowly learning for…5 years?..now, and this is the first time I’ve felt like the type system and associated `Base` functions prevent me from doing something natural. So I must be confused about why this would be useful, right?

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [February 8, 2021, 1:14pm UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/8 "2021-02-08T13:14:45Z")

</div>

> [@evanfields](#):
>
> having a function that maps like `Set{Int} -> Set` or `Vector{String} -> Array` seems…pretty handy

I am not sure about this (apart from interactive exploration). It is still not clear to me what the use case is.

Generally, it is not good design to decompose _generic_ types to name + parameters, as it does not always make sense (eg `BitVector <: AbstractVector{Bool}`, but has no such type parameter). It is better to design a lightweight API for these things. See `similar`.

---

<div class="post-metadata">

**Author:** ![evanfields](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/evanfields/32/1744_2.png) [@evanfields](https://discourse.julialang.org/u/evanfields)\
**Post date:** [February 12, 2021, 2:10am UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/9 "2021-02-12T02:10:05Z")

</div>

Not sure this is the best example—maybe the answer is there is no good example, so this missing feature isn’t important, but—

Suppose I wanted to write a function `larger_collection` that took two inputs collections `c1` and `c2`. Both inputs have to have the same “structural type”, e.g. `Set`, `Vector`, etc. `larger_collection` simply returns whichever collection is larger. But, in this case, I want it to be an error to compare collections of different “outer types”; eg. you can’t compare a `Vector` to a `Set`. So I could write definitions for each collection type I know about:

```julia
larger_collection(c1::AbstractVector, c2::AbstractVector) = ifelse(length(c1) > length(c2), c1, c2)
larger_collection(c1::AbstractSet c2::AbstractSet = ifelse(length(c1) > length(c2), c1, c2)
larger_collection(c1::AbstractDict, c2::AbstractDict) = ifelse(length(c1) > length(c2), c1, c2)
# etc

```

But this seems pretty repetitive, and won’t work for collection types I haven’t seen before. It’d be nice to write

```julia
function larger_collection(c1, c2)
    if outer_type(c1) != outer_type(c2)
        error(...)
    end
    length(c1) >= length(c2) && return c1
    return c2
end

```

or even, less ideally because of the `BitVector` point you correctly make, something like (this won’t parse, as far as I can tell)

```julia
function larger_collection(c1::S{T}, c2::S{R}) where {S, T, R}
    length(c1) >= length(c2) && return c1
    return c2
end

```

It seems like Julia doesn’t support the second or third options, leaving users with the more repetitive and less generic first option.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [February 12, 2021, 7:58am UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/10 "2021-02-12T07:58:48Z")

</div>

I think one difficulty in this example is guessing at which level the type should be the same. That is, you write `larger_collection(c1::AbstractVector, c2::AbstractVector) = ...` which works for e.g. `c1::SArray` and `c2::SparseVector`. It’s not clear whether `larger_collection(c1::S{T}, c2::S{R})` (if implemented) should work for such types, or be constrained to the same concrete type (`c1::SArray` and `c2::SArray`), or some other level of hierarchy (`StaticArray`? `AbstractSparseVector`? `Any`?).

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [February 13, 2021, 2:25pm UTC](https://discourse.julialang.org/t/parametric-type-conversion-without-eval-repetition/54308/11 "2021-02-13T14:25:18Z")

</div>

> [@evanfields](#):
>
> I want it to be an error to compare collections of different “outer types”; eg. you can’t compare a `Vector` to a `Set` . So I could write definitions for each collection type I know about: […] But this seems pretty repetitive, and won’t work for collection types I haven’t seen before.

I am not sure I understand the example fully (since you are using abstract types, this includes subtypes you don’t necessarily know about), but you probably want _traits_. Eg

```julia
struct VectorLike end
struct DictLike end

# users can extend these beyond subtype relations
structural_type(::Type{T}) where {T <: AbstractVector} = VectorLike()
structural_type(::Type{T}) where {T <: AbstractDict} = DictLike()

function larger_collection(a, b)
    _larger_collection(structural_type(a), structural_type(b), a, b)
end
_larger_collection(A::T, B::T, a, b) where T =
    length(a) > length(b) ? a : b
_larger_collection(A, B, _, _) =
    error("every time you try to compare $A to $B a kitten dies")

```
