Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

computed subtyping declarations #8322

Open
rfourquet opened this issue Sep 12, 2014 · 8 comments
Open

computed subtyping declarations #8322

rfourquet opened this issue Sep 12, 2014 · 8 comments
Labels
speculative Whether the change will be implemented is speculative

Comments

@rfourquet
Copy link
Member

Three things that would be nice to have:

  1. type Foo1{T} <: T end
  2. type Foo2{T} <: (T==Int ? Signed : Unsigned) end
  3. type Foo3{C}; a::C{Int} end

There are numerous related discussions on the lists, but I wanted to know if these requests are just not yet implemented or are "won't fix".
The problem is that at method/type definition time, type parameters are TypeVar objects and not the type they refer to at instanciation time.
The first time I hit this was when defining a method like fun{C}(collection::C, x::eltype{C}) = ... : there was no erros, because there is a fallback eltype(x) = Any, but this didn't do what I intended (this example seems related to point 2, but is maybe trickier to implement). Similarly it's unexpected that Foo2{Int}.super == Unsigned, this would deserve a clarification in the docs.
Another real use case of point 2. just came up from Bill Hart on julia-dev:

abstract Ring
abstract EuclideanDomain <: Ring 
abstract Field <: EuclideanDomain
type Polynomials{R<:Ring} <: (R<:Field ? EuclideanDomain : Ring) end

My hope that this gets implemented comes from the type section of the manual: "because Julia is a dynamically typed language and doesn’t need to make all type decisions at compile time, many traditional difficulties encountered in static parametric type systems can be relatively easily handled" :)

@JeffBezanson
Copy link
Member

The problem with 1 and 2 is that Foo1 and Foo2 are themselves types, so they need a place in the type hierarchy. With this extension Foo1 by itself would not make sense as a type, which would be a very dramatic change.

In case 3, C{Int} is a problem since we don't know that type C will even have parameters. However this case is a bit more tractable, since it's not so important to know field types for un-instantiated types. In inference, we could just assume they are Any unless we know C.

@rfourquet
Copy link
Member Author

For case 3, the very notation a::C{Int} could be a constraint that C is a parameterizable type. But more simply, like in C++ for so-called template template parameters, a special syntax may be used? e.g. type Foo3{C{}}; a::C{Int} end. I would favor having inference assume they are Any as you suggest, it seems no worse that the following work-around:

type Foo3{CI}
    a::CI
    Foo3(a) = (@assert eltype(CI) == Int; new(a))
end

For cases 1 and 2, I of course didn't think of the problem you point, but the same solution of making Foo1 and Foo2 subtypes of Any isn't applicable?

@JeffBezanson
Copy link
Member

Making Foo1 a subtype of Any might work; I'll have to think about it.

@rfourquet
Copy link
Member Author

Thanks. I realize I didn't give my real use case. My first try was relying on 3:

type SetLike{T, UnderlyingContainer} <: AbstractSet{T}
    cont::UnderlyingContainer{T}
end

So my second try was naturally using 2:

type SetLike{ConcreteUnderlying} <: AbstractSet{eltype{ConcreteUnderlying}}
    cont::ConcreteUnderlying
end

So I seem to not be able to express that my SetLike{T} is an AbstractSet{T}, unless I hardcode the underlying used container. This is not a big deal, but I like generality, whose price is unfortunately a loss in expressivity in this case.

@andreasnoack
Copy link
Member

@JeffBezanson I can give two more examples where these ideas are relevant. In #8176, we have been discussing if an array view should be a DenseArray or an AbstractArray. Here it could make sense with something like

View{T,S<:DenseArray{T}}<: DenseArray{T}
View{T,S<:AbstractArray{T}}<:AbstractArray{T}

or maybe even

View{T,S<:AbstractArray{T}}<:super(S)

In this thread, we discussed the possibility of storing a matrix of dual numbers as Dual{Matrix{Float64}} in order to leverage BLAS for the matrix arithmetic. However, that is not possible when Dual{T<:Real}<:Number. It would be great if it was possible to add something like the definition Dual{T<:AbstractMatrix{Float64}}<:AbstractMatrix{Float64}.

It appears to me that, in terms of the type lattice, this possibility is not invalid, although it changes the lattice structure a bit. It is different from the present system, but I think it could make sense that the location in the type hierarchy of a parametric type depends on the values of the parameters.

@mschauer
Copy link
Contributor

A simpler but related problem is arithmetic with integer type parameters, e.g. a trajectory in D dimensions is a D+1 dimensional object

immutable DynamicState{D}
    y :: Array{Float64,D}
end
immutable DynamicPath{D}
     yy :: Array{Float64,D+1}
end

@JeffBezanson JeffBezanson added the speculative Whether the change will be implemented is speculative label Sep 13, 2016
@JeffBezanson JeffBezanson changed the title Feature request: compile time type computation computed subtyping declarations Sep 13, 2016
@timholy
Copy link
Member

timholy commented Feb 6, 2020

If it were available, ColorTypes would use

abstract type Colorant{T,N} end    # T is element type, N is number of color channels
abstract type Color{T, N} <: Colorant{T,N} end    # types with no alpha channel
abstract type TransparentColor{T,N,C<:Color{T,N-1}} <: Colorant{T,N} end

where the N vs N-1 is due to the alpha channel.

Or even better:

abstract type Colorant{T,N::Int} end    # T is element type, N is number of color channels
abstract type Color{T,N::Int} <: Colorant{T,N} end    # types with no alpha channel
abstract type TransparentColor{T,N::Int,C<:Color{T,N-1}} <: Colorant{T,N} end

since that would allow one to express the fact that N must be a concrete Int.

@ParadaCarleton
Copy link
Contributor

ParadaCarleton commented Aug 24, 2023

Another important use case: units are often incompatible with currently-existing code, because units subtype Number, not Real. For example, we could replace the type declaration for units with:

struct UnitfulNumber{T <: Number} <: supertype(T)
    field::T
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
speculative Whether the change will be implemented is speculative
Projects
None yet
Development

No branches or pull requests

6 participants