You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Carbon currently has a design for internal implementation of interfaces where the API of that interface is included into the API of the type doing the implementation. It also has the concept of conditionally implementing interfaces. When combined, these create difficult concepts to resolve in name lookup that motivate considering different approaches to the design.
The idea is that Vector(T) implements Printable only when T does (perhaps so that the generic Vector type can delegate to Ts implementation. That, in isolation, makes sense. But it also appears to conditionally include the method Vector(T).Print based on whether T implements Printable which in turn would mean that name lookup into Vector(T) would depend on which interfaces T implements, which seems very undesirable for name lookup. Generally, we would like to have a separation of concerns here to decouple the question of the names found when looking into Vector(T)'s scope, and the conditional implementation of interfaces.
I think there are three broad approaches here:
We can make name lookup conditional and give up on the separation of concerns here. That would make the current design "just work" but seems like a unnecessarily complex and difficult model.
We can simply disallow conditional implementation of internal interfaces. This would mean that conditional implementation can only be done externally which has none of these problems.
We can fully separate these features, allowing a type to separately extend its API with an interface and then only conditionally implement that interface. This would allow name lookup to succeed unconditionally, but in cases where the names were in fact not implemented it would produce an error.
So far, we've been pursuing (3) with hypothetical syntax discussed in the context of #995 that looks something like:
class Vector(T:! type) {
// ...
// Extend the API to include that of `Printable` but without
// providing or requiring an implementation. Using this API
// when an implementation is not available would be an error.
extends Printable;
}
// Conditionally implement the API for certain `T`s.
impl forall [U:! Printable] Vector(U) as Printable {
// ...
}
However, none of the syntaxes discussed have really ended up satisfactory. The syntax extends Printable seems like too strong of a statement and implies that in fact the interface would be implemented.
This is also a generally advanced feature that we don't expect to be widely used and yet it would end up with very convenient (but unlikely to be desired) syntax in the language.
Another approach, let's call it (3b), is to use some form of aliasing facility to achieve this. We can almost do this without anything (and so in approach (2) above) today:
class Vector(T:! type) {
// ...
alias Print = Printable.Print;
}
// Conditionally implement the API for certain `T`s.
impl forall [U:! Printable] Vector(U) as Printable {
// ...
}
In some ways, (3b) seems like the cleanest approach from a design perspective. However, to achieve the goal of making it easy to replicate common and expected collections of APIs for a type it would need a special way to automatically alias the entire interface. If we solve that, the semantic model here seems the most clean approach proposed here. We could imagine an aliasing approach analogous to extends impl (as suggested syntactically for non-conditional cases in #995) such as: extends aliasing Printable;.
However, after the open discussion, I've thought more about this and am largely unconvinced that we need to pursue this at all. I feel like the use case we're chasing here of composing commonly associated units of API is unlikely to be widespread when conditional.
I'd like to propose we instead go with (2) and disallow internal conditional implementation. Then we can see whether in practice this is something we want, especially because we can work-around the limitation manually with aliases where desired. What do folks think about this?
The text was updated successfully, but these errors were encountered:
I'd like to propose we instead go with (2) and disallow internal conditional implementation. Then we can see whether in practice this is something we want, especially because we can work-around the limitation manually with aliases where desired. What do folks think about this?
Let's call this decided. None of the leads have expressed concern here and we've discussed several times. This decision is also very safe -- it is a conservative stance that is easily reversed if and when we get information that motivates that, and it is in fact structured to try and ensure we do get that information if it is a problem.
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>
Summary of issue:
Carbon currently has a design for internal implementation of interfaces where the API of that interface is included into the API of the type doing the implementation. It also has the concept of conditionally implementing interfaces. When combined, these create difficult concepts to resolve in name lookup that motivate considering different approaches to the design.
Details:
Today you can write code like the following:
The idea is that
Vector(T)
implementsPrintable
only whenT
does (perhaps so that the genericVector
type can delegate toT
s implementation. That, in isolation, makes sense. But it also appears to conditionally include the methodVector(T).Print
based on whetherT
implementsPrintable
which in turn would mean that name lookup intoVector(T)
would depend on which interfacesT
implements, which seems very undesirable for name lookup. Generally, we would like to have a separation of concerns here to decouple the question of the names found when looking intoVector(T)
's scope, and the conditional implementation of interfaces.I think there are three broad approaches here:
So far, we've been pursuing (3) with hypothetical syntax discussed in the context of #995 that looks something like:
However, none of the syntaxes discussed have really ended up satisfactory. The syntax
extends Printable
seems like too strong of a statement and implies that in fact the interface would be implemented.This is also a generally advanced feature that we don't expect to be widely used and yet it would end up with very convenient (but unlikely to be desired) syntax in the language.
Another approach, let's call it (3b), is to use some form of aliasing facility to achieve this. We can almost do this without anything (and so in approach (2) above) today:
This almost works in the explorer, but crashes: https://carbon.compiler-explorer.com/z/xh5x7nxj7
In some ways, (3b) seems like the cleanest approach from a design perspective. However, to achieve the goal of making it easy to replicate common and expected collections of APIs for a type it would need a special way to automatically alias the entire interface. If we solve that, the semantic model here seems the most clean approach proposed here. We could imagine an aliasing approach analogous to
extends impl
(as suggested syntactically for non-conditional cases in #995) such as:extends aliasing Printable;
.However, after the open discussion, I've thought more about this and am largely unconvinced that we need to pursue this at all. I feel like the use case we're chasing here of composing commonly associated units of API is unlikely to be widespread when conditional.
I'd like to propose we instead go with (2) and disallow internal conditional implementation. Then we can see whether in practice this is something we want, especially because we can work-around the limitation manually with aliases where desired. What do folks think about this?
The text was updated successfully, but these errors were encountered: