-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Generics external impl versus extends #995
Comments
The "consistently use
... so I find I much prefer consistently using I think I'm OK with having
That said, I'm not sure whether it's useful from an interface design perspective to introduce this choice between whether an interface requires an internal impl of another interface and whether it extends that interface (where the choice only affects implementers and not users). (One related thing I think we should keep in mind is that we might want to reserve syntactic space to allow interfaces to require an impl on a type other than
... or maybe as a big stretch something like:
But I think all the options in front of us are OK from this perspective: they all have an [Edit: |
can be expressed using a
But this example is weird since it uses |
We triage inactive PRs and issues in order to make it easier to find active work. If this issue should remain active or becomes active again, please comment or remove the |
We've been discussing an alternative that is more consistent:
The changes in this alternative are:
Using this alternative, the example would be written:
Conditional impls would be accomplished by using
Discussions: |
A question about this new alternative is how you would control name lookup into mixins. |
Notes from a discussion with @zygoloid on this topic:
We considered a possible addition, if we go with the
However, this had two problems: (1) there are no export keywords in the definition of Concern: the defaults are "public" and "not extends" consistent with other Carbon constructs, though we expect "private" & "extends" to be the common case.
Still always having Question: would we want a short cut for the common case, like we do for Observation: it is meaningful to write Question: Would we want to move
Alternative is:
|
Now with @chandlerc , we considered this might lead to:
as a shortcut for:
Which is reasonably intuitive / self-descriptive, even though it is a bit of keyword salad. We also talked about moving
This idea would also allow deriving from member types. |
Since there is some interest in this approach for this issue and for #1159 , I'd like to iterate on the details. We'd need a couple of keywords:
Starting with inheritance, a class may have at most one
The optional Examples:
An extends-declaration follows a similar pattern of supporting two forms:
The optional In an interface definition, the name would be of an interface, and the "declaration or definition" must be the declaration of an interface implementation requirement. Examples:
In a class, the name, declaration, or definition needs to be one of:
Note that there is no way (at this time) to combine an Adapter example:
Inheritance examples:
Interface implementation examples:
(Mixin examples covered earlier in this thread. I can go through them on request, but this comment has grown long enough.) |
While I think it will be common to want to both extend and derive from a class, it doesn't seem like an important common use case to support both deriving from and declaring a (nested) base class. Would it be ok to only have the first of these forms? Does that reduce any of the concerns? (It reduces one of mine which is having 3-levels of nested access specifiers.) |
I agree that removing the ability to declare a base class in a @chandlerc and I talked in person about restricting access-control specifiers, with the understanding that finer control is always possible by using multiple declarations instead of combining them.
It was important to me that if a declaration started with
We also talked about this example from earlier in the thread:
We agreed we didn't want to allow extending a final class, but wanted to leave the door open in the future to possibly doing something with this in the case @chandlerc was not excited about the spelling of Below the line are the updated versions of what I wrote earlier in this thread. A derived_from-declaration can have only have this form:
The optional Examples:
Classes Interface examples are not changed:
Adapter example is unchanged:
Inheritance examples:
Classes Interface implementation examples are unchanged:
Mixin examples now no longer allow two access specifiers, but there are two possible places to put an access specifier when writing
|
I had a talk with @mconst last night about this and he had a different perspective:
We talked about mixins in greater detail. @mconst thought:
@mconst is on vacation at the moment, but is available starting Tuesday. |
There have been some discussions recently about intentionally making the "requires interface/constraint to be implement" declaration in interfaces and constraints more distinct from the "implements interface/constraint" declaration in types, see for example https://discord.com/channels/655572317891461132/941071822756143115/1069691751968817283 . Some differences:
Possibly we could mirror the
Possibly we could do something like make |
We had a long discussion about this and related topics today (lots but potentially incomplete notes). I just wanted to try to capture the end result, as I think we got to some good consensus on the more major directional questions. However, there are a few details that might warrant further diving into and trying out some alternatives. For classes, I think we increasingly have a story we like, which I think largely addresses the part of this issue that also came up in #2293:
There is a related question in classes that we didn't get an answer we were clearly happy about: combining conditional conformance and API extension. But that is maybe worth factoring out into a separate issue as it seems fairly narrow and not something that should dramatically influence the rest of this picture. We also got fairly close to syntax for how analogous things show up in interfaces:
The idea is that The keyword We might want something shorter than I think that captures the outcome of the discussion we had, but @josh11b, @mconst, and @zygoloid -- please chime in if I got anything wrong. I'll separately add the parts of our discussion that really applied to other issues to those issues (#2495 and a new issue focused on the conditional conformance question). |
Filed #2580 to document the challenges of conditional internal implementation, the directions we discussed that could address that, as well as the downsides. FWIW, I'm suggesting there to just remove conditional internal implementation entirely and revisit it later if it proves an important feature. |
@josh11b mentioned we should record more of the rationale for each of the decisions that led to my summary outcome in #995 (comment) -- I'm going to use one post per point I recall so folks can react to them individually. First, we considered the case of a One reason we increasingly liked this solution was the move in #2360 to have things convert to a type when used in type position. This makes the use of |
Next, we looked at mixins themselves, largely considering the two leading approaches:
While (1) is very visually nice, we were a bit worried about users wanting to have Because the first decision handled non-API-contributing mixins completely and without any Some other reasons that (2) ended up being reasonably nice that I recall:
My feeling of the discussion is that option (2) was most concerning when we hadn't solved the non-API-injecting use case as we did. Once that was handled, the syntax in (2) seemed reasonably broadly appealing. While I've tried to list as much of the rationale here as I can, I don't feel like this was a case of a particularly sharp and clear-cut point that justified one option over the other. Instead, it seemed much more a balance between a lot of fairly small factors (in isolation) and subtle tradeoffs. Hopefully the summary here is useful and not inaccurate or misrepresenting the discussion in any way. |
The decision around A major part of the discussion here was about whether there was a way to eliminate the distinction between internal and external implementation of an interface. The existence of this distinction is the root cause of some of the complexity that bothered folks about all of the approaches here. For example, other languages don't have a distinction here. All interfaces implemented are available as-if internally. The downside is that this makes member name lookup fundamentally more complex (for example, depending on which implementations are imported). Carbon's design is avoiding this by using a much more explicit model of internal/external. But that necessarily adds some complexity -- implementing an interface requires thinking about which way to do it. We may find that even with the explicit difference, one or the other ends up being the dominant use case in Carbon. We might want to revisit our syntax here if that proves to be true, but I at least had a strong suspicion that we're going to see a mixture of trends here, where some code bases and some interfaces lean one direction or the other, and will end up needing to support both approaches as well as we can. Once we focused on supporting both use cases, and minimizing the cost of that, things converged much more I think. Specifically, the best way to support both use cases but minimize the cost is to make one a fairly minimal "decoration" of the other so that folks can pattern recognize all implementations using common syntax, and just add an extra marker when "internal". This rationale led pretty directly to settling on the
We considered a bunch of other permutations of words and syntaxes that have been mentioned in the past ( |
When looking at interface syntax... The
The Previously these have been modeled by mirroring the type's syntax for satisfying the requirement: We considered just allowing a
But this didn't read cleanly because it started with However, we immediately looked at the alternative of introducing the same grammar as
We didn't come up with anything that was interestly better to consider so we went with |
Since the discussion, I've also been thinking about one issue that was briefly discussed but didn't resolve one way or another, and I'd like to suggest we actually do this. Specifically, I think we should remove the
While in the discussion there was a desire for the shorthand to be part of the normal grammar for |
@chandlerc and @zygoloid came to an agreement today in an in-person meeting on class syntax. We considered three broad approaches:
Details of the discussion have been documented in the open discussion notes for 2023-02-07. The conclusion was to go with approach 2, at least for now. Looking in fewer places to see the API of a type was deemed an important property. Approach 2 was also deemed the simplest approach. The door was left open to later adopt approach 3 using a keyword (possibly Another question that was considered (though it was deemed "lower stakes" since the decision would be easier to change) was:
@chandlerc and @zygoloid agreed on the first choice. There were a few more points that we settled on:
The resulting syntax is:
Last question left for me: is there some sort of access control for the member name |
I think both @zygoloid and I are happy with I also think we can completely omit this for base classes for now. After talking it through we don't have any real use cases, and having the tokens Rationale for the position: putting And I think the answer for interface syntax haven't changed from my comment here: #995 (comment) Are there any other open corners here? |
An orthogonal aspect of all of this is whether it is spelled I think the overall feeling was we should use
|
I think this has actually converged, but trying to update examples... For the class syntax, slight updates to the earlier example:
And interfaces:
Interesting details:
Have I mis-remembered anything? (Happy for folks to edit this to be a useful summary.) The rationale here is pretty distributed as well but I do tihnk it is present and probably can just be consolidated in the proposal. |
FYI, I edited my summary example -- it no longer supposes an answer to shadowing outside of #2355 -- @josh11b filed the rest of that question to be tracked separately in #2745. I think the name conflicts were the only concern I've heard from a lead here and they should be separated now. Are we good to decide the syntax? |
Marking as decided. |
Update syntax of `class` and `interface` definitions to be more consistent. Constructs that add names to the class or interface from another definition are always prefixed by the `extend` keyword. Implements the decisions in: - [#995: Generics external impl versus extends](#995), - [#1159: adaptor versus adapter may be harder to spell than we'd like](#1159), - [#2580: How should Carbon handle conditionally implemented internal interfaces](#2580), and - [#2770: Terminology for internal and external implementations](#2770). Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Right now there is an inconsistency between how classes/adapters refer to interfaces they implement and interfaces/constraints to interfaces they require with respect to how they distinguish between whether they include the names from the implemented/required interface.
class
,adapter
impl
external impl
interface
,constraint
extends
impl
Should we make these consistent, and if so how? There are three options:
extends
: Consistently useextends
when including the names, drop theexternal
keyword.external
: Consistently useexternal impl
when not including names, stop using theextends
keyword for interfaces.extends
only used for base/derived classes.The text was updated successfully, but these errors were encountered: