Dlaczego kompilator Scala nie może wyświetlać ostrzeżenia o dopasowaniu wzorca dla niezamkniętych klas / cech?

10

Jeśli używam un uszczelniony traitlub abstract classw Scala, a następnie użyć dopasowywania wzorców, zastanawiam się, czy kompilator nie wiedzieć w czasie kompilacji dla tego konkretnego patternmatch co możliwe implementacje tej cechy / klasy są dostępne? Więc jeśli tak, to czy nie może dać ostrzeżeń o dopasowaniu wzorca, nawet jeśli trait/ abstract classnie jest zapieczętowane, ponieważ wie, które typy mogą być użyte, sprawdzając wszystkie możliwe zależności / import?

Np. Jeśli mam Option[A]i wykonuję dopasowanie wzorca tylko dla, Some[A]ale nie dla None, kompilator będzie narzekał, ponieważ Optionjest zapieczętowany.

Jeśli kompilator nie może tego wiedzieć / rozwiązać, dlaczego nie może? A jeśli kompilator (teoretycznie) może to zrobić, jakie są przyczyny tego, że nie jest używany w Scali? Czy istnieją inne języki, które wspierają takie zachowanie?

walenteria
źródło
Nie jest jasne, o co pytasz. Czy chcesz, aby kompilator emitował ostrzeżenie, jeśli wyrażenie dopasowania nie obejmuje wszystkich możliwych danych wejściowych? Być może przykład wyjaśniłby twoje pytanie.
kdgregory,
2
Jeśli ktoś może wprowadzić nową podklasę, dopasowanie wzorca nigdy nie będzie wyczerpujące. Np Państwo produkować jakąś abstrakcyjną klasę Fooz podklas A, Bi C, i wszystkie swoje mecze wzór pasuje tylko te trzy. Nic nie powstrzymuje mnie przed dodaniem nowej podklasy, Dktóra wysadzi twoje pasujące wzory.
Doval,
@kdgregory Tak, masz to. Dodałem przykład, aby wyjaśnić.
valenterry
3
Sprawdzanie całego importu nie jest wystarczające do wykrycia wszystkich możliwych podklas. Inna podklasa może zostać zadeklarowana w osobnym pliku klasy, który jest później ładowany podczas działania przez java.lang.ClassLoader.
amon

Odpowiedzi:

17

Wyodrębnienie wszystkich podklas klasy nazywa się analizą hierarchii klas, a wykonanie statycznego CHA w języku z dynamicznym ładowaniem kodu jest równoznaczne z rozwiązaniem problemu zatrzymania.

Ponadto jednym z celów Scali jest osobna kompilacja i wdrożenie niezależnych modułów, więc kompilator po prostu nie może wiedzieć, czy klasa jest podklasowana w innym module, ponieważ nigdy nie patrzy na więcej niż jeden moduł. (W końcu możesz skompilować moduł z interfejsem jakiegoś innego modułu, nawet jeśli ten moduł nie istnieje nawet w twoim systemie!) Dlatego właśnie sealedwymaga zdefiniowania wszystkich podklas w tej samej jednostce kompilacji.

Jest to również jeden z powodów, dla których maszyny JVM mogą tak korzystnie konkurować z kompilatorami C ++: kompilatory C ++ są zwykle kompilatorami statycznymi, więc ogólnie nie mogą dowiedzieć się, czy metoda jest zastąpiona, czy nie, i dlatego nie mogą jej inline. JVM OTOH, zwykle są dynamicznymi kompilatorami, nie muszą przeprowadzać CHA, aby dowiedzieć się, czy metoda jest nadpisana, czy nie, mogą po prostu spojrzeć na hierarchię klas w czasie wykonywania. I nawet jeśli na późniejszym etapie wykonywania programu pojawi się nowa podklasa, której wcześniej nie było, to nic wielkiego, po prostu ponownie skompiluj ten fragment kodu bez inlinizacji.

Uwaga: wszystko to dotyczy tylko Scali. JVM nie ma pojęcia sealed, więc można całkowicie podklasować sealedklasy z innego języka JVM, ponieważ nie ma sposobu na przekazanie tego w innym języku. sealedNieruchomość jest wpisana do ScalaSigadnotacji, ale kompilatory inne języki nie biorą pod uwagę te adnotacje, oczywiście.

Jörg W Mittag
źródło
3

To może być wykonane (przynajmniej dla wszystkich klas znane w czasie kompilacji), to jest po prostu drogie. Całkowicie zniszczyłbyś kompilację przyrostową, ponieważ wszystko, co zawiera dopasowanie wzorca, musiałoby być skutecznie rekompilowane przy każdej zmianie innego pliku.

A co kupujesz Zapach kodowy służy do pisania dopasowań wzorców, które muszą się często zmieniać po dodaniu nowej klasy pochodnej. Jest to naruszenie zasady otwartej / zamkniętej . Używaj dziedziczenia poprawnie i nie będziesz musiał pisać tego rodzaju dopasowań wzorców. I tak, zasada otwarta / zamknięta dotyczy również języków funkcjonalnych bez dziedziczenia klasowego. W rzeczywistości między funkcjami, takimi jak klasy typów, multimetody i zwykłe funkcje wyższego rzędu, języki funkcjonalne znacznie ułatwiają rozszerzanie bez modyfikacji.

Karl Bielefeldt
źródło
1
It can be done (at least for all classes known at compile time), it's just expensive.Ale jeśli program nie jest w 100% samowystarczalny (tzn. Zależy od .jarplików zewnętrznych ), czy nie mógłbyś wkraść się do nowej podklasy po kompilacji za pomocą jednego z nich jar? Kompilator może więc powiedzieć „Twoje dopasowania wzorca są teraz wyczerpujące, ale może się to zmienić, jeśli zmieni się którakolwiek z twoich zależności”, co jest dość bezwartościowe, ponieważ zależność zewnętrzna ma być w stanie zaktualizować je bez ponownej kompilacji!
Doval,
Stąd wyłączenie odpowiedzialności. W praktyce, jeśli jesteś wystarczająco mocno sprzężony, aby potrzebować wyczerpującego dopasowania zależnego, zewnętrznego lub innego, to i tak będziesz chciał ponownie skompilować.
Karl Bielefeldt,
@Doval W takim razie powinieneś użyć jakiejś formy delegowania i pozwolić, by wezwana klasa zdecydowała, co robić i odwrócić kontrolę. Do tego właśnie służy OOP. Jeśli tego nie chcesz, masz bieżący problem.
Sled,
@ArtB Nie rozumiem, co delegacja lub odwrócenie kontroli mają z tym wspólnego.
Doval,
Jeśli chcesz, aby działał z ludźmi, którzy mogą dodawać do niego zewnętrznie, to wywołaj klasę i przenieś logikę do tych klas.
Sled,