# Partially parametrized external constructor of a parametric type

**URL:** <https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687>\
**Category:** General Usage\
**Created:** [August 10, 2023, 5:38pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687 "2023-08-10T17:38:35Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![wsshin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wsshin/32/360_2.png) [@wsshin](https://discourse.julialang.org/u/wsshin)\
**Post date:** [August 10, 2023, 5:38pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/1 "2023-08-10T17:38:35Z")

</div>

Define a parametric type with parameters `S` and `T` (`S` is intended to be an integer, and T is intended to be a floating-point number):

```julia
struct FooBar{S,T}
    x::S
    y::T

    FooBar{S,T}(x,y) where {S,T} = new(x,y)
end

```

Define an external constructor with only the second parameter `T`:

```julia
FooBar{T}(y) where {T} = FooBar{Int,T}(0,y)

```

The external constructor works:

```julia-repl
julia> FooBar{Float64}(1.0)
FooBar{Int64, Float64}(0, 1.0)

```

Now, define the same parametric type and external constructor, except that `S` and `T` are limited with their supertypes as `S<:Integer` and `T<:AbstractFloat`:

```julia
struct Foo{S<:Integer,T<:AbstractFloat}
    x::S
    y::T

    Foo{S,T}(x,y) where {S,T} = new(x,y)
end

Foo{T}(y) where {T} = Foo{Int,T}(0,y)

```

The external constructor generates an error:

```julia-repl
julia> Foo{Float64}(1.0)
ERROR: TypeError: in Foo, in S, expected S<:Integer, got Type{Float64}
Stacktrace:
 [1] top-level scope
   @ REPL[14]:1

```

Here, the parameter `T` is the first (and only) parameter used in the signature of the external constructor. Somehow Julia seems to think that it should also be the first parameter of the type definition and therefore it has to be `<:Integer`.

To verify this hypothesis, define another parametric type and external constructor, but use the first parameter `S` of the type definition in the signature of the external constructor:

```julia
struct Bar{S<:Integer,T<:AbstractFloat}
    x::S
    y::T

    Bar{S,T}(x,y) where {S,T} = new(x,y)
end

Bar{S}(x) where {S} = Bar{S,Float64}(x,1.0)

```

Now the external constructor does not generate an error:

```julia-repl
julia> Bar{Int}(0)
Bar{Int64, Float64}(0, 1.0)

```

Is this a bug in Julia? Here is the version info:

```julia
julia> VERSION
v"1.9.2"

```

---

<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:** [August 10, 2023, 7:18pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/2 "2023-08-10T19:18:39Z")

</div>

Type parameters are not named, they are positional only. So, the convention that Julia adopts is that you can only drop trailing type parameters. You can’t choose to, say, omit the first parameter and only use the second parameter. Here is an example that demonstrates this:

```julia
julia> struct Foo{A,B,C} end

julia> Foo{Int}
Foo{Int64,B,C} where C where B

julia> Foo{Int,String}
Foo{Int64,String,C} where C

```

Note that I ran that example on Julia 1.4. At some point the printing was changed, which obfuscates what is going on. Here is what you see on Julia 1.8:

```julia
julia> struct Foo{A,B,C} end

julia> Foo{Int}
Foo{Int64}

julia> Foo{Int,String}
Foo{Int64, String}

```

I don’t know how to recover the full `UnionAll` type printing on recent versions of Julia.

---

<div class="post-metadata">

**Author:** ![algunion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/algunion/32/51630_2.png) [@algunion](https://discourse.julialang.org/u/algunion)\
**Post date:** [August 10, 2023, 7:36pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/3 "2023-08-10T19:36:33Z")

</div>

> [@CameronBieganek](#):
>
> I don’t know how to recover the full `UnionAll` type printing on recent versions of Julia.

Use `Base.unwrap_unionall`:

```julia
struct Foo{A,B,C} end

Foo{Int} |> Base.unwrap_unionall
# will produce: Foo{Int64, B, C}

Foo{Int,String} |> Base.unwrap_unionall
# will produce: Foo{Int64, String, C}

```

---

<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:** [August 10, 2023, 7:42pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/4 "2023-08-10T19:42:44Z")

</div>

Thanks! I actually created a separate thread for that question:

> [@Recover full printing of \`UnionAll\` type](https://discourse.julialang.org/t/recover-full-printing-of-unionall-type/102693):
>
> As discussed in [this thread](https://discourse.julialang.org/t/printing-of-unionall-types-is-misleading-as-of-julia-version-1-7/89418), printing of UnionAll types changed in Julia 1.7, as demonstrated here: 1.6 julia\> struct Foo{A,B} end julia\> Foo{Int} Foo{Int64, B} where B 1.7 julia\> struct Foo{A,B} end julia\> Foo{Int} Foo{Int64} Is there a way to recover the full UnionAll printing for Foo{Int}? Something like printunionall(Foo{Int})? (Obviously that doesn’t work, but you get the idea.)

---

<div class="post-metadata">

**Author:** ![wsshin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wsshin/32/360_2.png) [@wsshin](https://discourse.julialang.org/u/wsshin)\
**Post date:** [August 10, 2023, 7:50pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/5 "2023-08-10T19:50:43Z")

</div>

> [@CameronBieganek](#):
>
> Type parameters are not named, they are positional only.

I know this, but my question is not about the order of the parameters of a type: the question is about the order of the parameters of a constructor. That is why I gave the first example `FooBar`, for which it was shown possible to pass the first parameter `T` of the constructor to the second parameter of the type:

```julia
FooBar{T}(y) where {T} = FooBar{Int,T}(0,y)

```

---

<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:** [August 10, 2023, 8:19pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/6 "2023-08-10T20:19:50Z")

</div>

It’s the same underlying issue. Type objects are [callable](https://docs.julialang.org/en/v1/manual/methods/#Function-like-objects). The methods on type objects are called constructors. So `FooBar{Float64}(y)` is really shorthand notation for this:

```julia
(FooBar{Float64,T} where T)(y)

```

So when you have the `<:Integer` constraint on the first type parameter, `FooBar{Float64}(y)` will fail because `FooBar{Float64,T} where T` is not a subtype of `FooBar{S,T} where {S<:Integer, T<:Float}`. Thus, the type object cannot be created, and you get a failure before the constructor method is even called.

We can see this in action (on Julia 1.6, which has better printing):

```julia
julia> struct FooBar{S,T}
           x::S
           y::T
       
           FooBar{S,T}(x,y) where {S,T} = new(x,y)
       end

julia> FooBar{T}(y) where {T} = FooBar{Int,T}(0,y)

julia> @which FooBar{Float64}(1.0)
(FooBar{T, T1} where T1)(y) where T in Main at REPL[2]:1

```

Here you can see that the parameter `T` in the constructor you defined is used as the first parameter of `FooBar`, even though you use the _value_ of that parameter inside the method to set the value of the second parameter in the constructed object.

---

<div class="post-metadata">

**Author:** ![wsshin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wsshin/32/360_2.png) [@wsshin](https://discourse.julialang.org/u/wsshin)\
**Post date:** [August 10, 2023, 9:22pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/7 "2023-08-10T21:22:46Z")

</div>

I see. Then should I stop defining constructors like `FooBar{T}(y) where {T} = FooBar{Int,T}(0,y)` that use first parameters of the constructor as the later parameters of the type? Though this is doable if I don’t specify the supertypes of the type parameters, and convenient sometimes, it feels like exploiting a language loophole after reading your explanation.

---

<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:** [August 10, 2023, 9:35pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/8 "2023-08-10T21:35:52Z")

</div>

Yeah, I would avoid doing that. As you said, it works if the type parameters are not constrained, but it clashes with the semantics of `FooBar{Float64}` being equivalent to `FooBar{Float64, T} where T`.

---

<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:** [August 10, 2023, 11:09pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/9 "2023-08-10T23:09:16Z")

</div>

This is a big reason I wish it wasn’t possible to write partial parameters as a shorthand for iterated unions with unfixed trailing parameters, it’s very natural to wrongly assume the syntax instead means the parameters are like function arguments to a callable type-name. Even if you make a method for types that the `struct` definition made impossible, an error will be thrown at the impossible types like `Foo{Float64}`, before a call can even happen. Function arguments are the way to go, and make sure the callable is not impossible:

```julia
julia> Foo(::Type{T}, y) where {T} = Foo{Int,T}(0,y)
Foo

julia> Foo(Float64, 1)
Foo{Int64, Float64}(0, 1.0)

```

---

<div class="post-metadata">

**Author:** ![wsshin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wsshin/32/360_2.png) [@wsshin](https://discourse.julialang.org/u/wsshin)\
**Post date:** [August 10, 2023, 11:48pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/10 "2023-08-10T23:48:26Z")

</div>

> [@Benny](#):
>
> `julia> Foo(::Type{T}, y) where {T} = Foo{Int,T}(0,y)`

A tangential question, but is this different from the following?

```julia
Foo(T::Type, y) = Foo{Int,T}(0,y)

```

Both seem to generate the same result for `Foo(Float64, 1)`.

---

<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:** [August 10, 2023, 11:59pm UTC](https://discourse.julialang.org/t/partially-parametrized-external-constructor-of-a-parametric-type/102687/11 "2023-08-10T23:59:24Z")

</div>

I made the `T` a parameter of the _method_ shared with the parameter of the special `Type{T}`, it’s usually used to force the compiler to compile separate native code for each type `T`; Julia doesn’t automatically specialize on functions, types, or varargs that are only passed as parameters or arguments instead of being called themselves. Bit messy but you can see the effects:

```julia
julia> Foo(T::Type, y) = Foo{Int,T}(0,y)
Foo

julia> methods(Foo)
# 1 method for type constructor:
[1] Foo(T::Type, y) in Main at REPL[12]:1

julia> methods(Foo)[1].specializations # nothing compiled yet
svec()

julia> Foo(Float64, 1), Foo(Float32, 1), Foo(Float16, 1);

julia> filter(!isnothing, collect(methods(Foo)[1].specializations))
1-element Vector{Any}:
 MethodInstance for Foo(::Type, ::Int64)

julia> Foo(::Type{T}, y) where {T} = Foo{Int,T}(0,y)
Foo

julia> methods(Foo)
# 1 method for type constructor:
[1] Foo(::Type{T}, y) where T in Main at REPL[17]:1

julia> methods(Foo)[1].specializations
svec()

julia> Foo(Float64, 1), Foo(Float32, 1), Foo(Float16, 1);

julia> filter(!isnothing, collect(methods(Foo)[1].specializations))
3-element Vector{Any}:
 MethodInstance for Foo(::Type{Float16}, ::Int64)
 MethodInstance for Foo(::Type{Float32}, ::Int64)
 MethodInstance for Foo(::Type{Float64}, ::Int64)

```
