-
Notifications
You must be signed in to change notification settings - Fork 715
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
Glossary entries for "Subtype" and "Subclass" #4958
Conversation
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 may not be the right person to review this - maybe @leafpetersen?
As a general comment, we often talk about subtyping and subclassing as if the latter were a special case of the former, but I think this is a bit of a category mistake. We often get away with abusing the terminology a little, but we may want to be more precise here. (Part of the reason for this abuse is that "subclassing" is often used as a synonym for "implementation inheritance" and "subtyping" is used as a synonym for "interface inheritance" rather than subtyping in general.)
Strictly speaking, we have types and we have classes; subtyping is a relation on the former, and subclassing is a relation on the latter. We often refer to a type and a class by the same name, which can be convenient but also leads to conflating the two.
Not every subtyping relationship has to do with subclassing - for example, int
is a subtype of int?
, and String Function()
is a subtype of void Function()
.
The converse is a bit subtler. If I declare a class Foo
, I have also implicitly defined an associated type Foo
which instances of the class Foo
inhabit. These are two different logical entities, but there's a clear mapping from the class to the type, so in this sense, a subclass relationship also implies an associated subtype relationship. But we need to take care around generics: if class Foo<T> extends Bar<T>
, then Foo
is a subclass of Bar
(do we need to include the type arguments here? I'm not sure) but not every instantiation of Foo
is a subtype of every instantiation of Bar
.
src/resources/glossary.md
Outdated
|
||
## Subtype | ||
|
||
A _subtype_ is a class that implements another class |
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.
This is too narrow a definition. Many subtype relationships have nothing to do with classes. For example, T
is a subtype of T?
(for any type T
).
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.
Agreed. I think it makes sense to rely on the subtype relationship: Whenever we have T1 <: T2
according to the subtype rules, T1
is a subtype of T2
. class MyClass implements OtherClass ...
is just one special case.
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.
Let me just link the subtyping spec here for visibility. We could link to that from the glossary, but I don't know if we want to limit how technical these explanations are.
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.
Thanks @fishythefish that's helpful. We try not to link directly to the specs (like you said, too technical for general use) but it'll help my understanding a lot!
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.
One approach is to talk about this semantically rather than syntactically. That is, if I were writing something introductory on subtyping I'd say something like this: "A subtype is a type which is fully substitutable for another type. That is, it supports all of the operations of the supertype (along with possibly some extra operations). For example, if I have a type Animal, then the type Cat might be reasonably be a subtype of Animal since all Cats are Animals (but not vice versa). In practice, this means that a value of a subtype can be assigned to any location expecting the supertype, and all of the methods of the supertype are available on the subtype. Subtypes can be introduced for classes via implementation or inheritance. Other types, such as function types and nullable types, have structural subtyping: that is, they are judged to be subtypes based solely on the structure of the type. So for example, for any nullable type T?
, we say that T
is a subtype of T?
, based just on the structure of the type. "
I'm not so worried about the category mismatch: In every situation where we wish to topicalize implementation inheritance (that is, we're considering With that in mind, I tend to think that it's OK to categorize subtype relationships into (1) "subclass" relationships, established by If we wish to target subtype relationships that are specifically created by an I added a couple of comments in the files, too. |
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.
It looks fine to me, but I'm just adding a comment — such that I don't create any kind of unwanted interference.
src/resources/glossary.md
Outdated
|
||
## Subtype | ||
|
||
A _subtype_ is a class that implements another class |
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.
Agreed. I think it makes sense to rely on the subtype relationship: Whenever we have T1 <: T2
according to the subtype rules, T1
is a subtype of T2
. class MyClass implements OtherClass ...
is just one special case.
Agreed - I'm generally not too worried about the category mismatch either, and the intended meaning has been clear in almost every conversation that I've had. Maybe another way to phrase my concerns is that there are essentially two audiences: those wondering about subtyping generally (or using this as a reference to e.g. read a feature specification), and those wondering about interface inheritance specifically (especially if they're coming from another (object-oriented) language, e.g. Java). IMO, since this is a general-purpose glossary for Dart, we should mainly target the former. I think it would be okay to then mention that |
Great insight, I was operating under the impression that I think that we could mention |
Visit the preview URL for this PR (updated for commit 344c828): |
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.
Tricky stuff! ;-) I added a couple of comments.
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 think this one has been waiting for quite a while, and there were a lot of good discussion about it. So I'll approve, just in case it is helpful.
@eernstg I'm definitely still going to go through and incorporate the comments before merging, but thanks for the approval! Appreciate all the explanations here, tricky stuff indeed :) |
@fishythefish @leafpetersen @eernstg I rewrote both sections, taken basically word for word from the comments here (thank you all!) If anyone wants to take another look that would be greatly appreciated! Here are the staged sections: https://dart-dev--pr4958-subtype-subclass-iwh6vcvk.web.app/resources/glossary#subclass |
|
||
This is true at least statically. | ||
A specific API might not allow the substitution at run time, | ||
depending on its operations. |
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.
If it helps to have a specific example:
void f(List<num> l) {
l.add(3.14);
}
List<int>
is a subtype of List<num>
(this might be a good place to link to this FAQ or similar documentation), so f(<int>[1, 2, 3])
passes static checks, but fails at runtime. f(<num>[1, 2, 3])
works.
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.
Would ask that you check for active voice, shorter sentences and condition-action before merging, but that's non-blocking
Fixes #4784
Sparked from this comment.
I tried to explain the difference while still having them make sense as stand alone glossary entries. I'm not really sure I captured the nuance. @fishythefish since you gave the preliminary explanation, and @johnpryan since you asked the question, would you mind evaluating this? Are these definitions useful?
Based on the definitions I added in this PR, I'm now unsure about some of the uses of subclass vs. subtype in the main Class Modifiers page. E.g. This use of "subclasses" under
final
seems like it could be incorrect?:final
prevents bothimplements
andextends
, so it should be "...any subtypes..."?extends
on thefinal
superclass) need to be markedbase
,final
orinterface
, but not subtypes (declared withimplements
on thefinal
superclass)?I'm probably overthinking it; any thoughts are appreciated!
(Ignore the empty glossary page, we're working on filling it out and standardizing its use.)
ToDo: