# More flexible parametric types

**URL:** <https://discourse.julialang.org/t/more-flexible-parametric-types/129671>\
**Category:** General Usage\
**Tags:** parametric-types\
**Created:** [June 5, 2025, 1:03pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671 "2025-06-05T13:03:32Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![Joris\_Pinkse](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joris_pinkse/32/216398_2.png) [@Joris\_Pinkse](https://discourse.julialang.org/u/Joris_Pinkse)\
**Post date:** [June 5, 2025, 1:03pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/1 "2025-06-05T13:03:32Z")

</div>

I would like to have the functionality displayed in the definition of S₂. This code obviously does not work (and I understand why), but what would be the closest thing to accomplishing such functionality? Thanks!

**Background** : I (will) have a lot of related types that have a lot in common, each with many fields with varying types. It is much easier (to me!), less maintenance, and less remembering to have a single parametric type than to define many individual ones and use subtyping. I can cut down on the maintenance part by generating various types programmatically, but that is not great for legibility. Alternatively, I can do something like `struct S{T1,T2,T3,T4,T5}`, but that’s not great either. And yes, I do want parametric types.

_(I suspect I’m setting myself up for a hard no, but would appreciate any suggestions and ideas.)_

```julia
struct S₁{T₁, T₂}
    x :: T₁
    y :: T₂
end

struct T
end

struct X
end

const D = Dict( T => ( Int, Float64 ), X => (String, Int ) )

struct S₂{Q}
    x :: D[Q][1]
    y :: D[Q][2]
end

x = S₂{T}( 0, 1.0 )

# I want S₂{T} to give me the same as S₁{Int, Float64}

```

---

<div class="post-metadata">

**Author:** ![VinceNeede](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vinceneede/32/215744_2.png) [@VinceNeede](https://discourse.julialang.org/u/VinceNeede)\
**Post date:** [June 5, 2025, 1:20pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/2 "2025-06-05T13:20:06Z")

</div>

Do you actually need different structs? the easiest thing for me, given your example, would be to overload the constructor:

```julia-repl
julia> struct S₁{T1, T2}
           x::T1
           y::T2
       end

julia> struct T end

julia> struct X end

julia> S₁{Q}(x, y) where Q<:T = S₁{Int, Float64}(x, y)

julia> S₁{T}(3, 4)
S₁{Int64, Float64}(3, 4.0)

```

---

<div class="post-metadata">

**Author:** ![Joris\_Pinkse](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joris_pinkse/32/216398_2.png) [@Joris\_Pinkse](https://discourse.julialang.org/u/Joris_Pinkse)\
**Post date:** [June 5, 2025, 1:44pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/3 "2025-06-05T13:44:43Z")

</div>

> [@VinceNeede](#):
>
> `S₁{T}(3, 4)`

Thank you.

If the only objective were to create such objects then your method would indeed be great.

But I’d like to use it generically, so to also be able to use

```julia
function f( x :: S₁{T} ) where T
....
end

```

or something like that.

---

<div class="post-metadata">

**Author:** ![VinceNeede](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vinceneede/32/215744_2.png) [@VinceNeede](https://discourse.julialang.org/u/VinceNeede)\
**Post date:** [June 5, 2025, 2:28pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/4 "2025-06-05T14:28:47Z")

</div>

All you can do is to create aliases, like

```julia-repl
julia> struct S{T, Q}
           x::T
           y::Q
       end

julia> const SIF = S{Int, Float64}
SIF (alias for S{Int64, Float64})

julia> const SSI = S{String, Int}
SSI (alias for S{String, Int64})

```

But note that the core of type assertion is to know types at compile time. Something like

```julia
struct S₂{Q}
    x :: D[Q][1]
    y :: D[Q][2]
end

function f( s :: S₂{T} ) where T
    return s.x + s.y # or anything else
end

```

would go completely against that. `s.x` and `s.y` **compile time** types would depend on a variable `T` that is only known at **runtime**

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [June 5, 2025, 5:54pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/5 "2025-06-05T17:54:57Z")

</div>

Taking your question at face value, ComputedFieldTypes.jl kinda does what you want:

```julia-repl
julia> using ComputedFieldTypes

julia> struct T end

julia> struct X end

julia> const D = Dict(T => (Int, Float64), X => (String, Int))
Dict{DataType, Tuple{DataType, DataType}} with 2 entries:
  X => (String, Int64)
  T => (Int64, Float64)

julia> @computed struct S₂{Q}
           x::D[Q][1]
           y::D[Q][2]
       end

julia> S₂{T}
S₂{T}

julia> S₂{T}( 0, 1.0 )
S₂{T, Int64, Float64}(0, 1.0)

julia> dump(S₂{T, Int64, Float64})
struct S₂{T, Int64, Float64} <: Any
  x::Int64
  y::Float64

```

However, this seems like a possible XY problem:

- [https://xyproblem.info](https://xyproblem.info)

- [XY problem - Wikipedia](https://en.wikipedia.org/wiki/XY_problem)

---

<div class="post-metadata">

**Author:** ![Joris\_Pinkse](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joris_pinkse/32/216398_2.png) [@Joris\_Pinkse](https://discourse.julialang.org/u/Joris_Pinkse)\
**Post date:** [June 5, 2025, 7:01pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/6 "2025-06-05T19:01:31Z")

</div>

It solves X. I will continue to experiment trying to come up with better solutions for Y.

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [June 6, 2025, 10:34am UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/7 "2025-06-06T10:34:51Z")

</div>

Can you explain what you want a little more concretely? That is, one complete example of what you want to work.

One problem is that complex types (with many parameters) are somewhat sucky to write down. Type aliases can sometimes cut down on this work, but are not flexible enough for your job.

However, nobody prevents you from doing arbitrary computation on types at the time of definition. So you could do

```julia
struct S{T1,T2}
x::T1
y::T2
end
d = Dict(:T => S{Int,Float64}, :S => S{Int,Int})

_st = d[:T]
typeof(_st(1,2))
foo(x::_st) = 1

```

In other words, there is no reason to ever spell out the actual types; you can simply use normal code like your dict to plug together the types you want. It’s just that

```julia
foo(x::d[:T]) = ....
struct Bar x::d[:T] end

```

don’t work. That’s a design decision that forces you to spell out

```julia
tmp = d[:T]
foo(x::tmp) = ...

```

which makes it obvious that the type pattern that foo matches is computed in global/module scope one line before that, when the module is loaded, and that subsequent mutations of `tmp` or `d` will not change this definition.

A limitation is that the following won’t work:

```julia
julia> function bar()
       t = d[:T]
       function ff(x::t) 1 end
       t = d[:S]
       function ff(x::t) 2 end
       ff
       end
ERROR: syntax: local variable t cannot be used in closure declaration

```

And it’s clear why this cannot work: The definition of the closure must, after all, be lifted into global scope by lowering, and `t` is only known once `bar()` actually runs, not at the time of definition of `bar()`. (closure definitions are syntactic constructs that do not participate in local control flow)

---

<div class="post-metadata">

**Author:** ![Joris\_Pinkse](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joris_pinkse/32/216398_2.png) [@Joris\_Pinkse](https://discourse.julialang.org/u/Joris_Pinkse)\
**Post date:** [June 6, 2025, 3:14pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/9 "2025-06-06T15:14:50Z")

</div>

Thank you very much @foobar_lv2 .

All of this is just needed to avoid carrying around overly complicated type descriptions. I **don’t need** anything `like foo(x::d[:T])`.

Let me try to be precise in what the problem is. At the bottom is a type with many fields that vary on a total of (say) five types, e.g.

```julia
struct B{T1,T2,T3,T4,T5}
	F1 :: T1
	F2 :: Vector{ T1 }
	F3 :: T2
	....
	F30 :: T5
end

```

But I don’t need all possible combinations of `T1` through `T5`. What I need `B` for is to define a collection of types `S` that have the same set of fields, but differ in `T1` through `T5`. Basically, these contain data for use in different types of estimators, say `E1` through `E20`. Instead of adding a new name for every `S` and use type aliases, it seems cleaner to have a single type `S` like

```julia
struct S{E}
...
end

```

which maps to `B{T1,T2,T3,T4,T5}` for `T1` through `T5` that depend on `E`. This is all compile time-resolvable.

In other words, I want to be able to use it as any other type, including allowing for nesting

```julia
struct K{E}
	s	:: S{E}
	i	:: Int
	...
end

```

and using it in function arguments

```julia
function foo( x :: S{E} )
	...
end

```

So if there is a more elegant way of doing this than to use computed field types then I’m all ears.

---

<div class="post-metadata">

**Author:** ![FerreolS](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ferreols/32/216854_2.png) [@FerreolS](https://discourse.julialang.org/u/FerreolS)\
**Post date:** [June 6, 2025, 9:32pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/11 "2025-06-06T21:32:01Z")

</div>

I think it is linked to one of my [question about operation on type parameter](https://discourse.julialang.org/t/relation-between-type-parameter). I don’t think it is possible to make complex operations on type parameter to explicit link `E` with all different type `T1, T2,...`. However if `E` appears in one of this type parameter, ( let say `T1 == E` or `T1 = Array{N,E}` then you can create an alias as proposed by @VinceNeede and rely on inner constructor to enforce its structure:

```julia
struct B{T1,T2,T3,T4}
               F1 :: T1
               F2 :: Vector{ T1 }
               F3 :: T2
               F4 :: Complex{T1}
              #default
              B(f1::T1, f2::T2, f3::T3, f4::T4) where {T1,T2,T3,T4} = error("building B{$T1,$T2,$T3,$T4}")
              # T1 <: Real
              B(f1::T1, f2::Vector{T1}, f3::Int, f4::Complex{T1}) where {T1<:Real} = new{T1,Vector{T1},Int,Complex{T1}}(f1,f2,f3,f4)
end

S{E} = B{E,T2,T3,T4} where {T2,T3,T4}

```

Then you can dispatch on S{E} and the following function should work

```julia
function foo( x :: S{E} ) where E
	
end

```

If you don’t want to write all inner constructors for a large number of `E` then you can rely on generated functions.

---

<div class="post-metadata">

**Author:** ![Joris\_Pinkse](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joris_pinkse/32/216398_2.png) [@Joris\_Pinkse](https://discourse.julialang.org/u/Joris_Pinkse)\
**Post date:** [June 6, 2025, 9:36pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/12 "2025-06-06T21:36:38Z")

</div>

Thanks. This seems to be more or less what `ComputedFieldTypes` does.

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [June 6, 2025, 11:45pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/13 "2025-06-06T23:45:31Z")

</div>

> [@FerreolS](#):
>
> rely on inner constructor to enforce its structure

You don’t need inner constructors for this.

Inner constructors are really important only for uninitialized fields of object-reference type. Uninitialized object references are encoded as null-pointers, and currently the compiler emits checks on every access to potentially uninitialized reference fields.

So in julia, it is a slightly hidden property of types whether and which fields can be uninitialized; this cannot change later, so all constructors that can leave fields uninitialized must be visible to the compiler at once. This is why inner constructors exist as a language construct. Anything else doesn’t need inner constructors.

I personally would have preferred a `@maybe_undef` macro, but 🤷

---

<div class="post-metadata">

**Author:** ![FerreolS](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ferreols/32/216854_2.png) [@FerreolS](https://discourse.julialang.org/u/FerreolS)\
**Post date:** [June 7, 2025, 7:52am UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/14 "2025-06-07T07:52:26Z")

</div>

I don’t think so. I believe that inner constructors are more than that, it is the right tools to prevent the building of object that cannot be described by the alias ‘S{E}’. I can’t see better way to enforce that

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 7, 2025, 7:13pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/15 "2025-06-07T19:13:57Z")

</div>

> [@Joris\_Pinkse](#):
>
> But I don’t need all possible combinations of `T1` through `T5` . What I need `B` for is to define a collection of types `S` that have the same set of fields, but differ in `T1` through `T5` . Basically, these contain data for use in different types of estimators, say `E1` through `E20` . Instead of adding a new name for every `S` and use type aliases, it seems cleaner to have a single type `S` like
> 
> ```julia
> struct S{E}
> ...
> end
> 
> ```
> 
> which maps to `B{T1,T2,T3,T4,T5}` for `T1` through `T5` that depend on `E`. This is all compile time-resolvable.

As FerreolS implied with aliasing `S{E} = B{E,T2,T3,T4}`, you’re going beyond what type parameters do: types or limited values shared across subtypes, aliases, and fields. If a type parameter shows up in a field, it has to show up in the struct header. ComputedFieldTypes.jl only makes the inner parametric constructor for you to hide those parameters from source code and some printing, not the type system, so if you change how those field types are computed (e.g. mutating `D` entries in njasko’s earlier demo), the same expressions will start meaning different types that conflict with previously evaluated code in an intentionally obscured way. Parameters just aren’t mere name tags.

Name tags can just be appended to names, specifically that of aliases.

```julia
const SE_3 = B{T1_23, T2_57, T3_45, T4_29, T5_31}

```

And introduce the supertype with

```julia
struct B{T1, T2, T3, T4, T5} <: S

```

though while you can use `SE_3` and `S` in annotations, you can’t use `E_3` as a parameter to relate `S{E_3}` and `K{E_3}`. That’s a point in favor of putting up with the caveats of `@computed struct S{E}`, no need for a separate `B` there. Since you need to use `fulltype` to compute the full type when you’re not instantiating, wrappers like `K{E}` also need to be `@computed` and will hold `fulltype(S{E})` as a hidden parameter to match the field’s. Personally I wouldn’t define types for `E` if they won’t be instantiated, symbols like `:E1` are allowed as parameters and they won’t run into type instability stemming from inferring `DataType`.

---

<div class="post-metadata">

**Author:** ![Joris\_Pinkse](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joris_pinkse/32/216398_2.png) [@Joris\_Pinkse](https://discourse.julialang.org/u/Joris_Pinkse)\
**Post date:** [June 7, 2025, 9:07pm UTC](https://discourse.julialang.org/t/more-flexible-parametric-types/129671/16 "2025-06-07T21:07:06Z")

</div>

Thanks @Benny . I’m aware: good to have it here though.
