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

Defining alternate parametric orderings #36213

Open
omus opened this issue Jun 9, 2020 · 4 comments
Open

Defining alternate parametric orderings #36213

omus opened this issue Jun 9, 2020 · 4 comments
Labels
types and dispatch Types, subtyping and method dispatch

Comments

@omus
Copy link
Member

omus commented Jun 9, 2020

I've encountered a scenario where it would be very convenient to define an alternate definition of a parametric type when only a subset of the parameters are defined. An example of what I would like to have would probably be written as:

struct Interval{T,L,R}
    left::T
    right::T
end

const Interval{L,R} = Interval{T,L,R} where {L,R,T}

Where L and R are symbols defining if the left/right side of the interval are :open or :closed. Having such a definition would allow me to have a convenient way to write types such as:

Interval{T,L,R}  # e.g. Interval{Int,:open,:closed}
Interval{L,R}    # e.g. Interval{:open,:closed} == Interval{T,:open,:closed} where T
Interval{T}      # e.g. Interval{Int,L,R} == Interval{Int,L,R} where {L, R}

It should be noted that it is currently possible to define constructors that re-arrange the parameters in this way:

Interval{L, R}(left::T, right::T) where {T,L,R} = Interval{T,L,R}(left, right)
Interval{T}(left, right) where T = Interval{T,:closed,:closed}(convert(T, left), convert(T, right))

which works as expected:

julia> Interval{Float64}(1, 2)
Interval{Float64,:closed,:closed}(1.0, 2.0)

julia> Interval{:open,:closed}(1, 2)
Interval{Int64,:open,:closed}(1, 2)

But defining constructors such as these can be confusing if a user attempts to define a similar type in the same way:

julia> Interval{:open,:closed}
Interval{:open,:closed,R} where R
@omus
Copy link
Member Author

omus commented Jun 9, 2020

An alternative solution to this problem would be to put restrictions on the parametric types to avoid creating invalid type definitions and keep using a constructor to perform the parameter re-arranging. Such a solution could look like the following:

struct Bound{name} end
const Open = Bound{:open}
const Closed = Bound{:closed}
const Unbounded = Bound{:unbounded}

isbound(name::Symbol) = name in (:open, :closed, :unbounded)

struct Interval{T,L<:Bound,R<:Bound}
    left::T
    right::T
end

function Interval{L, R}(left::T, right::T) where {T,L,R}
    isbound(L) || error("Unsupported left-bound provided: $L")
    isbound(R) || error("Unsupported right-bound provided: $R")
    Interval{T,Bound{L},Bound{R}}(left, right)
end

However, since the type restrictions need to always be valid we are unable to call the custom constructor even though that constructor doesn't apply any restrictions:

julia> Interval{:open,:closed}(1,2)
ERROR: TypeError: in Type, in L, expected L<:Bound, got Symbol
Stacktrace:
 [1] top-level scope at REPL[9]:1

I can open a separate issue if this seems like a valid idea

@omus omus added the types and dispatch Types, subtyping and method dispatch label Jun 9, 2020
@iamed2
Copy link
Contributor

iamed2 commented Jun 10, 2020

For constructors, your example only possible because you are stealing another type parameter. If you put a bound on the type parameter in the struct definition, this will not work:

julia> struct Interval{T<:Real,L,R}
           left::T
           right::T
       end

julia> Interval{L, R}(left::T, right::T) where {T,L,R} = Interval{T,L,R}(left, right)

julia> Interval{T}(left, right) where T = Interval{T,:closed,:closed}(convert(T, left), convert(T, right))

julia> Interval{:open, :closed}(1, 2)
ERROR: TypeError: in Interval, in T, expected T<:Real, got a value of type Symbol
Stacktrace:
 [1] top-level scope at REPL[6]:1

@omus
Copy link
Member Author

omus commented Jun 10, 2020

I mentioned this in the comment above. Possibly the type parameter restrictions should only be applied when calling new and not when call an outer constructor.

@iamed2
Copy link
Contributor

iamed2 commented Jun 10, 2020

Ah you did, sorry.

I don't know if the compiler is currently using this information, but I do know that removing that restriction would not let the compiler assume some bound on the type parameter for the purpose of optimizing or inferring the code inside the method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
types and dispatch Types, subtyping and method dispatch
Projects
None yet
Development

No branches or pull requests

2 participants