-
-
Notifications
You must be signed in to change notification settings - Fork 48
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
Use Site Variance and Implicit Interfaces for Generic Types. #123
base: main
Are you sure you want to change the base?
Conversation
Nicely written RFC, thanks. A couple of points, about the text firts of all. Rather than calling the type type ElementsSourceis Elements[+Data]
type ElementsSink is Elements[-Data] Additionally, just for completeness could you add a method to Finally I think explicitly stating examples of what this means in terms of subtyping would be useful:
About the feature itself, I don't really like the |
|
||
# Alternatives | ||
|
||
- Create the anonymous interface as a trait instead, so that other types not mentioned at the use site won't end up as "accidentally" implementing the anonymous interace. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this wouldn't actually be a trait, but some mix between the two (an interface which requires the class name to be the same).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of it as an anonymous trait that was implicitly created, as well as being implicitly applied to your class, so that your class was the only type that was nominally linked to the trait.
I think this way of thinking about it is easier to understand than saying the the compiler checks the class name, but maybe others feel differently.
I used the name |
I'm hesitant to reserve new keywords for this, especially one as common in code as the word |
Without a really good reason, I would prefer to not add new keywords. I dont see much difference between except that a couple of those add new keywords. i think |
Some notes from today's sync. Is Cell[-A] an interface or not?A concern I had is that this conflates two distinct features, making an interface from a type and extracting the co/contra-variant subsets of types. The first one is not related to generics, and would be useful for all classes. Given the class class Foo
fun do_stuff() => ...
interface IFoo for Foo
interface IFooExplicit
fun do_stuff() The second, and the original point of the RFC is to create a co or contravariant version of a type. For example CellSink[A] is a contra-variant version of Cell[A]. This means class Cell[A]
var value: A
fun get(): A => value
fun set(value': A) => value = consume value'
type CellSink[A] is Cell[-A] We could keep these as two distinct features which can be combined. For example interface ICellSink[A] for Cell[-A]
class AlternativeCell[A]
fun get(): A => ...
fun set(value': A) => ... However @sylvanc convinced me that there's no legitimate use case for So the alternative described in this RFC is to make Unfortunately this means there's no way to do the "make an interface out of a type" without also making the interface co/contra-variant, such as the SyntaxAs I mentioned before, I find +/- quite meaningless, and always have a hard time to remember which one is which. I suggested the in and out are obviously very popular names, and we wouldn't want to make them reserved keywords. However, since types must begin with an uppercase, in and out are already not allowed in a type parameter position. This allows us to make them "contextual keywords", where they are normal identifiers when used as function/variable names, but a keyword when used where a type is expected. This would be a first for Pony, and a bit of a Pandora's box. edit: I had screwed up the direction of subtyping (obviously), hopefully fixed now |
Here's a suggestion on the "is Cell[-A] an interface or not" question. We add the syntax On top of this, we add the +/- (or in/out) syntax, to specify co/contravariant type arguments. These are only allowed on a ~ type. These are allowed anywhere a type is allowed, including type aliases. type IFoo is ~Foo
type ICellSink[A] is ~Cell[-A]
type ICellSource[A] is ~Cell[+A] |
Another thing I've been wondering is what happens to methods which have the type argument in both argument and return position. Let's say the class Cell[A]
var value: A
fun get(): A => value
fun set(value': A): A^ => value = consume value' In this case, what does the fun set(value': A): Any
fun set(value': A): None The first requires This actually applies to all co-variant methods are still included in contra-variant interfaces. interface ICellSink[A]
fun get(): Any
fun set(value': A): Any The get method can be called, but the return value cannot be used. Technically we could do the same thing the other way round, interface ICellSource[A]
fun get(): A
fun set(value': Bottom): A But we don't have a Bottom type, and the set method could never be called, so it's useless to include it |
So we discussed this a bit more with @sylvanc and @Theodus over the weekend, and we've come to the conclusion that this may not be a feature we want in the end. First of all, and maybe the biggest red flag, is that automatic extraction of an interface from a class makes it impossible for libraries to add methods while maintaining backwards compatibility. If classes Foo and Bar both only have methods We've don't have any definition of what change we want to allow to be backwards compatible, but adding new methods to classes surely seems uncontroversial. More generally, and without even taking backwards compatibility into account, classes usually have a much broader surface than interfaces do. In other words, rather than imposing the full set methods, we should encourage people to define exactly what set of methods they require as an Interface. |
I think the main reason you probably reached that conclusion is the emphasis placed on the "extracting an interface from a type" part of the feature, which as far as I'm concerned is not the main focus. My original purpose for trying to push this idea forward was always with the intention of having a convenient type for interacting with different reifications of the same generic type. @sylvanc put forth the idea that it could be an open interface, allowing for any viable type to fill it. I have no problem with this approach, but that was never my goal. Every time I've wanted to have something like So I don't think the issue you raised in your last comment is really a problem in that paradigm. If you think it would help prevent confusion about the purpose of the feature, we could consider adding the restriction you were advocating for earlier, where our type system will only allow reifications of the named type rather than acting as an open interface. This would also remove the need for the |
I think I understand @jemc 's use case better now, and I think this is more in the vein of higher-kinded types. Let's think about expanding this RFC to be a higher-kinded types for Pony RFC. I propose we all have a think and chat about this some more. |
Rendered.