Get long type information for struct definition

Sometimes I want to wrap a struct which has long type information in another struct. For example, I want to wrap an interpolator like this one:

julia> using Interpolations

julia> cubic_spline_interpolation(0:0.1:0.2, zeros(Float64, 3)) |> typeof
Interpolations.Extrapolation{Float64, 1, ScaledInterpolation{Float64, 1, Interpolations.BSplineInterpolation{Float64, 1, OffsetArrays.OffsetVector{Float64, Vector{Float64}}, BSpline{Cubic{Line{OnGrid}}}, Tuple{Base.OneTo{Int64}}}, BSpline{Cubic{Line{OnGrid}}}, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}, BSpline{Cubic{Line{OnGrid}}}, Throw{Nothing}}

The type information is very long, so I like to get the type automatically instead of typing the long type information by hand.

struct Foo
itp::typeof(cubic_spline_interpolation(0:0.1:0.2, zeros(Float64, 3)))
something_else
end

But quite often I want a parametric type so that the interpolator can be used for real or complex numbers. Ideally, I want something like the following:

struct Foo{T}
itp::typeof(cubic_spline_interpolation(0:0.1:0.2, zeros(T, 3)))
something_else
end

But this definition won’t work, as T is just a placeholder in this definition and thus the function call zeros(T, ...) doesn’t work.

One solution might be

struct Foo{ITP}
itp::ITP
end

but I don’t like this one, as then the Struct Foo would have long type information.

Is there a way to solve this problem? Thanks.

One option may be:

I’d just go with struct Foo{X} x::X end, though.

1 Like

Thank you.

At this moment, I just use two different types as those are all I need.

I really hope julia can support computing type information as the package does. As not just for the example I gave above, another common case is that the struct contains multiples collections with different lengths like N+1, N, 2N+1… This also needs to ability to compute the type information.

Would this work for you?

struct Foo{T} where {T <: Interpolations.Extrapolation}
   itp::T
end
1 Like

I got an error.

julia> struct Foo{T} where {T <: Interpolations.Extrapolation}
          itp::T
       end
ERROR: syntax: invalid type signature around REPL[2]:1
Stacktrace:
 [1] top-level scope
   @ REPL[2]:1

That’s unfeasible because symbols can refer to different functions in different modules and a generic function can change its behavior and results dynamically, but a type and its instances’ structure cannot. As a consequence, those type parameters you’re trying to condense are necessary for adapting to method changes. That massive (and as far as I’m aware, internal) Extrapolation type is rooted in many method calls, and as far as the language is aware, any of them can change behavior and result in a different return type of cubic_spline_interpolation, even though nobody would do that in practice.

ComputedFieldTypes is no exception, it omits the full type parameters from the constructor methods, but they still must exist and are still printed that way. Here’s an earlier example. I’m not certain, but I think it is hypothetically possible for unchangeable and pure built-in functions like <: to allow very limited field type computation.

Let’s simplify your example. You effectively want to do something like:

julia> struct Foo{T}
         vec::Vector{T} # longer to type than T
       end

julia> Foo([1])
Foo{Int64}([1])

but with an instantiating method instead of explicitly typing the type out, like

struct Foo{T}
  vec::typeof(ones(T, 1)) # not valid code
end

But this is impossible because Foo{MyType} becomes several concrete types with different vec field types depending on how I redefine Base.ones(::Type{MyType}, dims) (don’t do this in practice). The only way for Foo{MyType} to be a valid type is to have vec::Any to accommodate all possible results of the field type computation. Going back to your example, itp::Any is necessary to adapt to various cubic_spline_interpolate calls in practice because the concrete type of itp also depended on the 0:0.1:0.2 input; even if we pretended methods were set in stone so parameters could be condensed, T for the zeros input was not enough. You can make a Foo constructor that computes T from itp before calling Foo{T}(itp, args...).

Used to think the same, but I don’t see this as a priority any more. Keep in mind Julia’s type system is already enormously powerful, but kinda difficult to maintain. Both bugs and performance issues exist regarding the type system and the compiler in general which should be fixed before making it yet more complicated and powerful. What you’re suggesting would be a really big project in terms of effort and risk, but with comparatively little in the way of benefits.

Yeah, I was thinking about the template system of C++, but this may not be a good idea in general…

Sorry, I used the wrong parametric format for structs. That should have been:

struct Foo{T <: Interpolations.Extrapolation}
   itp::T
end

Thank you. The code works, but the type information is still long, which is what I want to avoid.