Jak nic nie jest podtypem każdego innego typu w Scali

19

Biorę kurs Martina Oderskiego na programowanie funkcjonalne ze Scalą i na razie nauczyłem się dwóch rzeczy, które razem nie mają sensu:

  1. Scala nie obsługuje wielokrotnego dziedziczenia
  2. Nothing jest podtypem każdego innego typu

Te dwa stwierdzenia nie mogą żyć razem, więc jak dokładnie to się robi? i co dokładnie oznacza „podtyp każdego innego typu”

Edytuj 1

W API Scala , Nothingjest zdefiniowany jako abstract final class Nothing extends Any... więc jak można go przedłużyć innych klas?

vainolo
źródło
Ta strona może trochę pomóc: artima.com/pins1ed/scalas-hierarchy.html
jhewlett 23.04.13
O ile widzę, to jest definiowany jako „ostateczna cecha Nic rozciąga Any” scala-lang.org/api/2.7.6/scala/Nothing.html
Den
8
Mylisz typy i klasy. Te dwie rzeczy są bardzo różne. Niestety, nie jesteś jedynym, który jest zdezorientowany tym rozróżnieniem, i naprawdę niestety niektórzy z tych, którzy są zdezorientowani, są projektantami popularnych języków, takich jak Java, C # i C ++. Nie mówi, że Nothingjest to podklasa każdej innej klasy. Mówi, że jest to podtyp każdego innego typu .
Jörg W Mittag
1
@delnan: Interfejsy Java są pobierane bezpośrednio z protokołów Smalltalk. W Smalltalk tylko protokoły są typami, klasy nie. W Javie zarówno interfejsy , jak i klasy są typami. To jest źle. Klasy nie są typami, są tylko interfejsy. Nie ma znaczenia fakt, że wszystkie te języki mają różne typy, a nie klasy. Problem polega na tym, że w tych językach klasy są typami, co jest złe.
Jörg W Mittag
1
@ JörgWMittag To jest inne stwierdzenie i niezwykle dyskusyjne (zwykle zgadzam się, że jest szkodliwe, ale nie przypisuje tego nieporozumieniu podczas pisania). Nie ma sensu omawiać tego tutaj.

Odpowiedzi:

27

Składanie i dziedziczenie to dwie różne rzeczy! Nothingnie rozszerza wszystkiego, jest podtypem , rozszerza się Any.

Opis [§3.5.2] ma szczególny przypadek zarządzającego podtypy-związek Nothing:

§3.5.2 Zgodność

  • [...]
  • Dla każdego typu wartości
    T,scala.Nothing <: T <:scala.Any
  • Dla każdego konstruktora typu T(z dowolną liczbą parametrów typu)
    scala.Nothing <: T <: scala.Any
  • [...]

Gdzie w <:zasadzie oznacza „jest podtypem”.

Jak to się dzieje: nie wiemy, to magia kompilatora i szczegóły implementacji.

Dość często język robi rzeczy, których nie potrafisz jako programista. Jako odpowiednik Nothing: Wszystko w Scali dziedziczy Any, wszystko oprócz Any . Dlaczego Anyczegoś nie dziedziczy? Nie możesz tego zrobić. Dlaczego Scala może to zrobić? Cóż, ponieważ Scala ustalił zasady, nie ty. Nothingbycie podtypem wszystkiego jest po prostu innym tego przykładem.

phant0m
źródło
10
BTW: jest to dokładnie to samo, co nullprzypisanie do pola każdego typu w Javie. Dlaczego to jest możliwe? Czy nulljest instancją każdej klasy? Nie, jest to możliwe, ponieważ kompilator tak mówi. Kropka.
Jörg W Mittag
8
Gdybym mógł to głosować sto razy, zrobiłbym to. Mylące typy i klasy to jedna z najgorszych rzeczy, jakie przyniosły nam języki takie jak Java.
Jörg W Mittag
1
Dla ciekawskich dusz na temat różnicy między dziedziczeniem a podtypami cmi.ac.in/~madhavan/courses/pl2006/lecturenotes/lecture-notes/... jednak nie kupuję tego - jeśli odziedziczysz (jak extendsw Javie, a nie jako komponuj) ) w końcu robisz to dla podtypu.
greenoldman
11

Kiedy mówi, że Scala nie obsługuje wielokrotnego dziedziczenia, wówczas odnosi się do dziedziczenia implementacji metody wiele razy. Oczywiście można zaimplementować wiele interfejsów / cech w klasie, a nawet zdefiniować tę samą metodę, ale nie występuje konflikt między różnymi implementacjami z powodu linearyzacji cechy.

Ogólnie, jeśli masz klasę C1z metodą f()i klasę C2również z metodą f(), to wielokrotne dziedziczenie oznacza, że ​​możesz w jakiś sposób odziedziczyć obie implementacje f(). Może to prowadzić do różnych problemów, które Scala rozwiązuje, pozwalając ci odziedziczyć po jednej klasie, aw przypadku wielu cech, wybierając jedną implementację na podstawie kolejności cech.

Co do Nothingrzeczy, są naprawdę proste, ponieważ nic nie ma zdefiniowanych atrybutów ani metod. Więc nie możesz mieć żadnych konfliktów spadkowych. Ale zakładam, że większość twoich niespodzianek wynika z innego zrozumienia wielokrotnego dziedziczenia.

Kiedy zrozumiesz, że linearyzacja cech skutecznie eliminuje jakąkolwiek dwuznaczność dziedziczenia, i że nie mówimy o dziedziczeniu z wielu cech jako wielokrotnego dziedziczenia z tego powodu, powinieneś być w porządku.

Jak to się dzieje: kompilator jest ostatecznie za to odpowiedzialny. Zobacz specyfikację języka Scala w rozdziale 3.5.2, która obejmuje między innymi:

For every type constructor T (with any number of type parameters), scala.Nothing <: T <: scala.Any.

Innymi słowy, jeśli chcesz poprawnie wdrożyć kompilator, musi on obsługiwać Nothingjako podtyp wszystkiego według specyfikacji. Z oczywistych powodów Nothingnie zdefiniowano, aby obejmowało wszystkie klasy załadowane do systemu, ale istotność zdefiniowania Nothingjako podtyp jest ograniczona do wszystkich miejsc, w których istotne jest podtypowanie.

Ważną kwestią jest tutaj to, że nie istnieje żadna instancja typu Nothing, dlatego jej leczenie jest ściśle ograniczone do sprawdzania typu, co leży w sferze kompilatora.

Szczery
źródło
2
Nadal nie rozumiem, jak to się robi ... Zobacz edycję mojego pytania
vainolo
1
„Znaczenie zdefiniowania Nic jako podtypu jest ograniczone do wszystkich miejsc, w których podtyp jest istotny”. Co chcesz z tym przekazać? X jest istotne, gdzie X jest istotne?
phant0m