0

This is me probably going off what is considered idiomatic in Julia, as I am clearly abusing parametric types (generic types instead of hardcoded ones) but I am struggling to understand why this doesn't work and what are my alternatives.

Say we have this parametric type {T}:

struct mystruct{T}
    val::T
end

I can restrict the possibilities by supertyping T:

struct mystruct{T<:Number}
    val::T
end

But I don't seem able to restrict it to a certain type:

struct mystruct{T::Int64}
    val::T
end

My goal is being able to declare several fields at once by declaring the generic struct with Int64 as I did above. So I'm looking for the <: equivalent.

My end goal is to do this:

struct mystruct{T::Int64}
    val::T
    max::T
    mystruct(val::T) = begin
    new(val, typemax(T))
    end
end

Instead of what I currently do:

struct mystruct
    val::Int64
    max::Int64
    mystruct(val::Int64) = begin
    new(val, typemax(val)) # or typemax(Int64)
    end
end

4 Answers 4

1

I don't see much utility in restricting parametric types to one type, but you can do so by restricting the type in your internal constructor:

julia> struct mystruct{T}
           val::T
           max::T
           mystruct(v::T) where T <: Int64 = new{T}(v, typemax(v))
       end

julia> mystruct(4)
mystruct{Int64}(4, 9223372036854775807)

julia> mystruct(n) = mystruct(Int64(n))
mystruct

julia> mystruct(Int32(5))
mystruct{Int64}(5, 9223372036854775807)
 
Sign up to request clarification or add additional context in comments.

3 Comments

Uh a new construct! But how's this different from declaring mystruct(v::Int64) = new{typeof(v)}(...)? I had to use typeof because for some reason I don't currently understand, T is given as not defined.
See the discussion on Parametric Composite Types above. Honestly, the simplest way is to drop the parameterization and just make mystruct.v and mystruct.max to be declared to be Int64.
If you really want to keep the restriction in the parameter for struct, this also works: struct mystruct{T <: Int64}; val::T; max::T; mystruct(v::T) where T = new{T}(v, typemax(v)); end; # (you need the where T in the function to make sure T is in scope for new)
1

As you can read in the section on Parametric Composite Types of the Julia manual and the following section Julia allows you to define types that have parameters. Such types have names Type{TypeParameters}.

Note that, on purpose I concentrate only on type name not on whole type definition as it is irrelevant. What is important to understand here that the whole construct Type{TypeParameters} defines a type, i.e. the {TypeParameters} part is part of the type. Here is an example:

julia> struct Type{Int}
       end

julia> t = Type{Int}()
Type{Int64}()

julia> typeof(t)
Type{Int64}

Note that type parameter, in general does not have to be associated with fields of a composite type:

julia> struct Type1{T<:Int}
           val
       end

julia> Type1{Int}("a")
Type1{Int64}("a")

julia> Type1{Union{}}("b")
Type1{Union{}}("b")

The parameters of a type are just a way to bundle together multiple types sharing some common structure and functionality.

Now note:

julia> struct Type2{T<:Int}
           v::T
           Type2{T}() where {T} = new()
       end

julia> Type2{Int}()
Type2{Int64}(1609504000)

julia> Type2{Union{}}()
Type2{Union{}}(#undef)

julia> Type2{Any}()
ERROR: TypeError: in Type2, in T, expected T<:Int64, got Type{Any}

Here I used incomplete initialization to actually create an instance of Type2{Union{}} type that has a field of type Union{} although it is impossible to properly initialize it.

See:

julia> x = Type2{Union{}}()
Type2{Union{}}(#undef)

julia> fieldtypes(typeof(x))
(Union{},)

I additionally run Type2{Any}() constructor to show you that the type restriction of struct is enforced.

In summary - in general type parameter is just a part of the type name. Then when defining types writing Type{<:SomeType} is just Julia analogue of covariant type. Just as Type{>:SomeType} is analogue of contravariant type. See here for an explanation. Also maybe discussion of UnionAll types might be helpful for you to understand this topic.

Comments

1

This sounds a bit like a solution in search of a problem—ultimately, doesn't your issue boil down to Int64 being too long to type?

If so, you could just define a type alias

const T = Int64

at the top of your code.

If T is different every time, you could use a let block like this:

julia> let T = Int64
           struct MyStruct
               a::T
               b::T
               c::T
           end
       end

julia> MyStruct(4, 5, 6)
MyStruct(4, 5, 6)

Then T is no longer in scope, which is probably what you want:

julia> T
ERROR: UndefVarError: T not defined

Finally, although I can't see a reason for it, you can also have a parametric struct whose parametric type is just Int64: struct MyStruct{Int64} a::Int64 end.

And note that T::Int64 and T<:Int64 aren't really analogous in the sense that t == 5 and t <= 5 are. The visual similarity is misleading: In T<:Int64, T is a type that is a subtype of Int64, whereas in T::Int64, T is a value of type Int64. To keep yourself on the right track, you should make sure that whatever you write before :: is in lowercase, which customarily denotes a value rather than a type.

4 Comments

Ah ah! A SISP in the wild. Actually it's not cause I had the problem first, but my solution suggestion might be the weird thing. I did not think about declaring it a variable, I love that even more!
What are the implications of parameterising here struct MyStruct{Int64} a::Int64 end? I don't see how it's different from just having a::Int64, but I'd like a pointer to understand it. I tried to do struct MyStruct{Int64} a end but it just doesn't work.
struct MyStruct{Int64} a end with proper line breaks works, although I can't get it to show in a comment. This is simply a type with only one field, namely a, that can take values of any type. The {Int64} bit is superfluous, and really only serves to make it harder to create instances of the type: MyStruct(4) errors whereas MyStruct{Int64}(4) works.
ah, that's it, Julia doesn't care about linebreaks I just didn't know I had to do MyStruct{Int64}(4.5). That also answers my question,a has nothing to do with the type parametrization. I'm sure there's a use case for this construct though.
0

I just noticed that subtypes(Int64) returns nothing so in my case {T<:Int64} already does what I wanted.

For types which are not at the end of their hierarchy, an abstract type such as Signed, it wouldn't make sense to do what I am asking: {T::Signed}

So it's a non issue.

8 Comments

Union{} <: Int64 is a difference. It might be relevant in some cases (not in this though).
What is that exactly?
I mean that when you use subtyping with <: you potentially allow Union{} type. This is sometimes problematic with multiple dispatch. In your case it is not a problem as Union{} type has no values of this type. Here is a brief example with an empty collection Union{}[] isa Vector{<:Int64} produces true.
No values can have Union{} type and that is why your case is immune, because your struct is immutable and has some elements. E.g. struct SomeStruct{<:Int64} end (without any element) would not be immune to this problem.
I will add an answer as it seems more explanation is needed.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.