-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
constraints on non-type as type parameters #9580
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
Comments
This is not a high priority, since it doesn't prevent anybody from getting work done. |
I agree, I couldn't really think of a situation that this can affect the correctness of valid code either. It's only a little bit confusing when I did sth wrong and the error pops up in unrelated place etc... |
the Top and Undef types are gone now. its unclear to me if |
Well it would definitely seem reasonable that T would be constrained to be
a type in the former case, but I don't know enough about the internals to
day whether it would be reasonable to change it to work like that.
|
Yeah, I relized that Conceptually |
I guess it requires a field in |
At some point I was also thinking that it might be nice to be able to write sth like
So that the first parameter has to be a type and the second has to be a The (only) benefit would be less confusion and less manual checking if one want to be safe but I'm not sure how big a change it requires in the underlying system... Edit: I am aware that |
This idea has come up before (a long time ago I believe), I also think that
it would be a good addition (and don't know how hard it would be).
|
As far as I can tell, the Julia documentation does at the moment not explicitly state, what values are permitted as type parameters. All the examples I found show only other types as type parameters. Are e.g. integer values also allowed as type parameters? Any immutable value that can be created at compiler time (e.g. a text string)? A good practical application example of an integer parameter would be a parametric modular-arithmetic type, where the modulus is compiled into the code, rather than being encoded as a run-time parameter along with each value. Imagine an application that does a lot of modular arithmetic, but only ever with the three fixed modulus values 5, 7 and 17. It would be great to be able to implement that via a single parametric "type Mod{modulus::Int} v::Int; end", which the programmer can then instantiate as types Mod{5}, Mod{7} and Mod{17} and add with "+(x::Mod{N}, y::Mod{N}) = Mod{N}(mod(x.v+y.v, N))"? At the moment, the latter results in "ERROR: UndefVarError: N not defined". |
Please ask questions about usage on our discourse message board. For documentation, see the first section of the page you linked, the last bullet point. Integers as type parameters are fully supported and frequently used. That's how we encode the dimensionality in our arrays. In fact, your exact example is in the The thing you're running into is that Julia does not support isa ( |
@yuyichao Could another benefit of the
I'm not sure that deserves its own feature request separate from this, but it is a feature I would find useful for writing more readable code. One simple example would be for making space-time arrays, since there's always a single implicit time dimension but any number of space dimensions, and I want to have edit: Though then what would PlusTwoD{Int,3}.parameters return? Maybe this is a separate (if dependent) request? |
The implementable subset of it is #18466 |
What was the ultimate outcome here? Are non-type parameters still under consideration or not? |
Non-type parameters are fully supported and always will be. They simply must be This issue is about "abusing" the syntax |
Ok, the way I ended up here was that I was trying to parametize a struct based on a UInt8 value, something like this:
(I guess I was taking a bit too much inspiration from C++ non-type template params where you have to specify the type) Just now I tried not specifying the type of N:
That seemed to work, though it doesn't convey the constraint that N should be a unsigned integer of some sort. Also, can that non-type parameter 'N' be used in an inner constructor inside the struct definition? EDIT: yes that seems to be possible:
but the 'where' seems a bit odd there. |
Right, we currently don't support |
This issue seems to have become a placeholder for the general idea of allowing additional constraints on non-types as type parameters, but FWIW it looks like the original complaints are all fixed now:
|
Fair enough that we don't really need this issue to know about the general idea of wanting to express constraints on values used in type parameters |
This was closed by mistake, the issue of Also, the title is misleadingly ambiguous, perhaps it should be changed to something like "<:Any constraint doesn't require subtyping, having no effect". Otherwise people will keep thinking this issue is about "wanting to express constraints on values used in type parameters". |
Furthermore, the same is true of UnionAll constraints like in this type definition:
Sadly this behaves as if there's no constraint, while the expected behavior would be to require that So it seems there's currently no workaround. |
We do not parse variable names, so AnyType<:Any is not different from Any, even with the change to the name, and is still representative of the supertype of all values of any kind |
This issue was about fixing the root of the type system, so that it is |
How about changing an explicit |
That would likely be spelled |
You completely misunderstood the proposal, the intent behind is precisely to avoid large breaking changes by adding another abstract type. |
I think whatever the final solution is, it's better to reopen this issue so that it won't be forgotten after a few weeks and sit in the corner, not fixed until another person rediscover the problem. I can already see some potential features/existing packages that can be affected by the outcome. |
We're not adding some pseudo type whose "subtypes" are values. We can just fix this. If a parametric type is declared as |
Interestingly, |
That is contradictory. If Any does not describe all values, then it is not the supertype of all values anymore, and thus there must exist a new element (it used to be called Top) that is the supertype of all values. But you have gained much confusion and breakage then with no gain. |
Could you elaborate more on why there should be a type that's the supertype of all values as opposed to just all types (as a subset of all values)? Is such a relation (between this "top" type and any value instead of any type) actively required and very pervasive in the source code of Julia? |
Since there must be a type that represents the maximal set of all type-query, thus the supertype is a type. But all types are values, thus it is a supertype of all values. Using non-type values as parameters is rather common in julia, whether to describe the length of a tuple, the size of an array, or the fields of a named tuple. |
I think you made a logical mistake here. "All types are values" does not necessarily mean "all values are types". Thus, even if we need a type to be the supertype of all types (though every possible type query), it does not necessarily mean we need a type to be the supertype of all values. Furthermore, having non-type values as parameters for composite types does not require having a supertype of all values either. Because those non-type values are always wrapped by the composite type as a container, hence are not directly exposed to the subtype evaluation ( |
I didn't make a logical mistake, but did forget to state the additional condition that some values are types. And if you don't have a type that is the supertype of all values, then I can take
|
Thanks for providing the reference! I'll have a read! Meanwhile, can you give an instance of From what I just checked, every element inside the
|
Personally, I also disagree with allowing |
It also leads to this slight oddity (though Core.Compiler tries to filter out uninhabited tuple types to avoid this from causing crashes)
This is not usually a concern of type systems. For example, in Rust, this is used to have a Never type, which cannot have instances, but is distinct from other uninhabited types or the bottom type. |
It seems to me that maintaining your insistence on having
Meanwhile, you still haven't provided a scenario (that can already be realized in the current version of Julia) of creating a type
which no user would encounter unless they deliberately try to construct This also brings up another interesting inconsistency:
which is another direct contradiction to insisting on |
This does not mysteriously break isa, because types are already values. The isa query is fine with this. No, the Val example just shows that type parameters are invariant and that Any is not a subtype of 1. |
I admit I made a mistake here. For two parametric types with the same type names, the type relation between the parameters does not infer the relation between the two parametric types unless the supertype parameter is a However, this does not affect my overall argument. I understand that, at this point, my discussion with @vtjnash has become somewhat unproductive, and I don't intend to convince or continue bothering them. So I write down my latest (and hopefully definite) thoughts here for whoever still wants to or will jump into this annoying little corner of Julia's current type system. I really don't know why you really should hold on to using |
Another example of what would be logically inconsistent is |
I don't see this as something that cannot amend. Even though the
where even though As for The one side effect I can think of now is for the type query of |
Maybe I'm reading it wrong, but the Julia subtyping paper seems to give some support to the argument against Here are some quotes from the paper that are relevant to this discussion:
Some observations:
|
That was quite a debate. I hope we can agree that I'd like to throw out some possibilities to test the ergonomics of what could be: Foo{T} where T # T can be any type or any isbits value
Foo{:} # synonym for `Foo{T} where T`
Foo{T} where T<:Any # T must be a type
Foo{<:Any} # synonym for `Foo{T} where T<:Any` degenerate cases: T where T # synonym for `Any`
T where T<:Any # synonym for `Any`
Tuple{1} <: Tuple{Any} # throw TypeError? or return false? Introducing type assertion Foo{n} where n::Int # n must be a value of type `Int`
Foo{::Int} # synonym for `Foo{n} where n::Int` but also allows these redundancies: Foo{::Type} # synonym for `Foo{<:Any}`
Foo{::Type{Int}} # synonym for `Foo{Int}`
Foo{::Type{<:Number}} # synonym for `Foo{<:Number}` It's attractive, but I'm not sure how to feel since |
Why was this closed? |
Will this issue be reconsidered/reopened anytime soon in the future? |
When a type parameter is specified to be
T <: Top
, it actually accept non-type as parameters as well.Although
1
and1.2
are clearly not subtypes ofTop
This is also the case for the builtin
Type
(some other types likeArray
doesn't even have this constraint specified)No error is raised even if this parameter is used as a type of a field
The same thing happens for
T <: Any
although this time it correctly rejectCore.Unref
Specifying other types seems fine
Another related issue is that
Vararg
doesn't specify any constraint on the parameter and therefore the following syntax is allowed.The text was updated successfully, but these errors were encountered: