2010-06-20 24 views
74

podczas kompilowania mój wniosek Haskell z opcją -Wall, GHC narzeka osieroconych przypadkach, na przykład:Osierocone przypadki w Haskell

Publisher.hs:45:9: 
    Warning: orphan instance: instance ToSElem Result 

Klasa typ ToSElem nie jest moje, jest zdefiniowana przez HStringTemplate.

Teraz wiem, jak to naprawić (przenieś deklarację wystąpienia do modułu, w którym zadeklarowano wynik), i znam why GHC would prefer to avoid orphaned instances, ale nadal uważam, że moja droga jest lepsza. Nie obchodzi mnie, czy kompilator jest niedogodny - raczej ode mnie.

Powodem, dla którego chcę zadeklarować moje instancje ToSElem w module wydawcy, jest to, że jest to moduł wydawcy, który zależy od HStringTemplate, a nie od innych modułów. Próbuję zachować oddzielenie obaw i uniknąć sytuacji, w której każdy moduł będzie zależał od HStringTemplate.

Pomyślałem, że jedną z zalet klas typu Haskell, w porównaniu na przykład do interfejsów Javy, jest to, że są otwarte, a nie zamknięte, dlatego instancje nie muszą być zadeklarowane w tym samym miejscu co typ danych. . Rada GHC wydaje się ignorować to.

Poszukuję więc jakiegoś potwierdzenia, że ​​moje myślenie jest zdrowe i że będę usprawiedliwiony ignorując/tłumiąc to ostrzeżenie lub bardziej przekonywującym argumentem przeciwko robieniu tego po swojemu.

+0

Dyskusja w odpowiedziach i komentarzach pokazuje, że istnieje duża różnica między definiowaniem instancji sierocych w pliku wykonywalnym *, tak jak to robisz, w bibliotece *, która jest narażona na inne. [To niezwykle popularne pytanie] (http://stackoverflow.com/q/26770247/) ilustruje, jak mylące instancje mogą być dla użytkowników końcowych biblioteki, która je definiuje. –

Odpowiedz

80

Rozumiem, dlaczego chcesz to zrobić, ale niestety, może to bądź tylko złudzeniem, że zajęcia Haskella wydają się być "otwarte" w sposób, w jaki mówisz. Wiele osób uważa, że ​​możliwość wykonania tego jest błędem w specyfikacji Haskell, z powodów, które wyjaśnię poniżej. W każdym razie, jeśli naprawdę nie jest właściwe dla instancji, która ma być zadeklarowana w module, w którym klasa jest zadeklarowana lub w module, w którym zadeklarowano typ, prawdopodobnie jest to znak, że powinieneś używać newtype lub jakiegoś innego otoki wokół swojego typu.

Powody, dla których należy unikać wystąpienia sierocy, są znacznie głębsze niż wygoda kompilatora. Ten temat jest dość kontrowersyjny, jak widać z innych odpowiedzi. Aby zrównoważyć dyskusję, zamierzam wyjaśnić punkt widzenia, że ​​nigdy nie należy pisać sierocych instancji, co moim zdaniem jest większościową opinią doświadczonych Haskellerów. Moja własna opinia jest gdzieś pośrodku, co wyjaśnię na końcu.

Problem wynika z faktu, że gdy istnieje więcej niż jedna deklaracja wystąpienia dla tej samej klasy i typu, nie ma mechanizmu w standardowym Haskell, który określałby, którego użyć. Zamiast tego program jest odrzucany przez kompilator.

Najprostszym efektem tego jest to, że możesz mieć doskonale działający program, który nagle przestałby się kompilować z powodu zmiany, którą ktoś inny popełnia w pewnej odległej zależności od twojego modułu.

Co gorsza, program roboczy może zacząć się od awarii w środowisku wykonawczym z powodu odległej zmiany. Możliwe, że używasz pewnej metody, która pochodzi z deklaracji konkretnej instancji i może zostać bezgłośnie zastąpiona przez inną instancję, która jest na tyle inna, aby spowodować, że twój program zacznie się w niewytłumaczalny sposób ulegać awarii.

Osoby, które chcą zagwarantować, że problemy te nigdy się z nimi nie spotkają, muszą przestrzegać zasady, że jeśli ktokolwiek i gdziekolwiek kiedykolwiek zadeklarował instancję danej klasy dla określonego typu, żadna inna instancja nie może być ponownie zadeklarowana w każdym programie napisanym przez każdego. Oczywiście istnieje obejście problemu z użyciem newtype do zadeklarowania nowej instancji, ale zawsze jest to co najmniej drobna niedogodność, a czasem poważna. W tym sensie ci, którzy świadomie piszą sierocą instancje, są raczej niegrzeczni.

Co należy zrobić w związku z tym problemem? Obóz anty-osierocony mówi, że ostrzeżenie GHC jest błędem, musi to być błąd, który odrzuca każdą próbę zadeklarowania instancji osieroconej. W międzyczasie musimy ćwiczyć samodyscyplinę i unikać jej za wszelką cenę.

Jak widzieliście, są tacy, którzy nie martwią się tymi potencjalnymi problemami. Właściwie zachęcają do korzystania z instancji osieroconych jako narzędzia do rozdzielania obaw, jak sugerujesz, i mówią, że należy po prostu upewnić się, że w każdym przypadku nie ma problemu. Byłem wystarczająco niedogodny z powodu sierocych instancji innych ludzi, aby być przekonanym, że ta postawa jest zbyt nieprzyzwoita.

Myślę, że właściwym rozwiązaniem byłoby dodanie rozszerzenia do mechanizmu importu Haskell, który kontrolowałby import wystąpień. Nie rozwiązałoby to całkowicie problemów, ale pomogłoby w ochronie naszych programów przed uszkodzeniami z istniejących już na świecie instancji sierocych. A potem, z czasem, mogę przekonać się, że w pewnych ograniczonych przypadkach być może sierota nie jest taka zła. (I ta pokusa jest powodem, dla którego niektórzy w obozie przeciw sierocym instancjom sprzeciwiają się mojej propozycji.)

Mój wniosek z tego wszystkiego jest taki, że przynajmniej na razie zdecydowanie zaleciłbym unikanie ogłaszanie jakichkolwiek osieroconych instancji, bycie rozważnym dla innych, jeśli nie z innego powodu. Użyj newtype.

+4

W szczególności jest to coraz większy problem z rozwojem bibliotek. Z> 2200 bibliotekami na Haskell i dziesiątkami tysięcy pojedynczych modułów, ryzyko zbierania instancji rośnie dramatycznie. –

+13

Re: "Myślę, że właściwym rozwiązaniem byłoby dodanie rozszerzenia do mechanizmu importowania instancji Haskella, który kontrolowałby import wystąpień". Jeśli pomysł ten interesuje kogokolwiek, może warto przyjrzeć się przykładowi z języka Scala; ma funkcje bardzo podobne do kontrolowania zakresu "implicits", które mogą być używane bardzo podobnie do instancji typu. – Matt

+3

Moje oprogramowanie jest aplikacją, a nie biblioteką, więc możliwość spowodowania problemów dla innych programistów jest praktycznie zerowa. Możesz rozważyć moduł Wydawca jako aplikację i resztę modułów jako bibliotekę, ale jeśli miałbym dystrybuować bibliotekę, to bez Wydawcy, a więc i osieroconych instancji. Ale jeśli przeniosłem instancje do innych modułów, biblioteka wysyłałaby niepotrzebną zależność od HStringTemplate. Więc w tym przypadku myślę, że sieroty są w porządku, ale posłucham twojej rady, jeśli napotkam ten sam problem w innym kontekście. –

38

Śmiało i zignoruj ​​to ostrzeżenie!

Jesteś w dobrym towarzystwie. Conal robi to w "TypeCompose". "chp-mtl" i "chp-transformatory" to robią, "control-monad-exception-mtl" i "control-monad-exception-monadsfd" to robią, itp.

btw pewnie już to wiesz, ale dla tych, które nie i nie potknąć się na swoje pytanie wyszukiwania:

{-# OPTIONS_GHC -fno-warn-orphans #-} 

EDIT:

uznaję problemów, które Yitz wymienione w jego odpowiedź jako rzeczywistych problemów. Jednak uważam, że nie używanie osieroconych instancji również jest problemem, i staram się wybrać "najmniejsze zło", które polega na ostrożnym stosowaniu sierocych instancji.

Użyłem tylko wykrzyknika w mojej krótkiej odpowiedzi, ponieważ twoje pytanie pokazuje, że już dobrze zdajesz sobie sprawę z problemów.W przeciwnym razie byłbym mniej entuzjastyczny :)

Trochę przekierowania, ale to, co uważam, że jest to idealne rozwiązanie w idealnym świecie bez kompromisów:

wierzę, że problemy Yitz wzmianki (nie Wiedząc, które instancja jest odbierany) może być rozwiązany w „całościowy” systemu programowania gdzie:

  • nie jesteś edycji zwykłe pliki tekstowe prymitywnie, ale są raczej wspierana przez środowisko (na zakończenie przykład kodu sugerować tylko rzeczy istotne typy itp.)
  • Język "niższego poziomu" nie ma specjalnego wsparcia dla klas typów, a zamiast tego tablice funkcyjne są przekazywane jawnie
  • Jednak środowisko programistyczne "wyższego poziomu" wyświetla kod w podobny sposób, jak przedstawiono teraz Haskella (zazwyczaj nie widać tablic funkcji przekazywanych wzdłuż) i wybiera jawne klasy typów, gdy są one oczywiste (na przykład wszystkie przypadki Functor mają tylko jeden wybór) i gdy istnieje kilka przykładów (lista do zapisania Aplikant lub lista -monad Applicative, First/Last/lift może Monoid) pozwala wybrać, której instancji użyć.
  • W każdym przypadku, nawet gdy instancja została automatycznie zbierane dla ciebie, środowisko łatwo pozwala zobaczyć, które instancja została wykorzystana, z łatwym interfejsem (interfejs hiperłącza lub najechania lub coś)

Powrót W tej chwili: Zalecam, aby unikać osieroconych instancji i nadal używać ich, gdy "naprawdę" potrzebujesz, aby

+4

Tak, ale prawdopodobnie każde z tych zdarzeń jest błędem jakiejś kolejności. Niepoprawne instancje w monadzie-wyjątku-mtl i monadach-fd dla kontroli Przypomnijmy. Byłoby mniej drażniącym, gdyby każdy z tych modułów był zmuszony do zdefiniowania swoich własnych typów lub dostarczenia nowych opakowań typu. Prawie każda instancja sierocy to ból głowy, który czeka, aby się wydarzyć, i jeśli nic innego nie będzie wymagało stałej czujności, aby upewnić się, że jest zaimportowana lub nie jest odpowiednia. –

+2

Dzięki. Myślę, że wykorzystam je w tej konkretnej sytuacji, ale dzięki Yitzowi teraz lepiej rozumiem, jakie problemy mogą powodować. –

16

W tym przypadku myślę, że wykorzystanie instancji osieroconych jest w porządku. Ogólna zasada jest dla mnie - możesz zdefiniować instancję, jeśli "posiadasz" typeklasy lub "posiadasz" typ danych (lub jego część - np. Instancja dla Może MyData też jest w porządku, przynajmniej czasami). W ramach tych ograniczeń, gdy zdecydujesz się umieścić instancję, to twoja własna firma.

Jest jeszcze jeden wyjątek - jeśli nie jesteś właścicielem typeklasy lub typu danych, ale tworzysz plik binarny, a nie bibliotekę, to też jest w porządku.

31

Osierocone instancje są uciążliwe, ale moim zdaniem czasami są konieczne. Często łączę biblioteki, w których typ pochodzi z jednej biblioteki, a klasa pochodzi z innej biblioteki. Oczywiście nie można oczekiwać, że autorzy tych bibliotek dostarczą instancji dla każdej możliwej kombinacji typów i klas. Więc muszę je zapewnić, a więc są sierotami.

Pomysł, że powinieneś owijać ten typ w nowy typ, gdy chcesz dostarczyć instancję, jest pomysłem o teoretycznej wartości, ale w wielu okolicznościach jest to po prostu zbyt żmudne; jest to rodzaj pomysłu ludzi, którzy nie piszą kodu Haskella na życie. :)

Więc śmiało i zapewniają osierocone instancje. Są nieszkodliwe.
Jeśli możesz zawiesić ghc z sierocymi instancjami, to jest to błąd i należy go zgłosić jako taki. (Błąd, który ghc miał/ma o braku wykrycia wielu wystąpień, nie jest trudny do naprawienia.)

Należy jednak pamiętać, że w przyszłości ktoś inny może dodać instancję, która już istnieje, i może pojawić się błąd (kompilacji).

+2

Dobrym przykładem jest '(Ord k, Arbitralne k, Arbitralne v) ⇒ Arbitralne (Mapa k v)' podczas korzystania z QuickCheck. –

3

W tym temacie rozumiem pozycje WRT na temat obozów anty-sierocych instancji, ale w przypadku wykonywalnego obiektu docelowego nie powinny występować przypadki osierocone?

+3

Jeśli chodzi o bycie niegrzecznym dla innych, masz rację. Ale otwierasz się na potencjalne przyszłe problemy, jeśli ta sama instancja kiedykolwiek zostanie zdefiniowana w przyszłości gdzieś w łańcuchu zależności. W takim przypadku decyzja, czy warto ryzykować, należy do Ciebie. – Yitz

+5

Prawie we wszystkich przypadkach wdrażania instancji osieroconej w pliku wykonywalnym, należy wypełnić lukę, którą * chciałbyś * już zdefiniować. Jeśli więc wystąpienie pojawi się na wyższym poziomie, wynikowy błąd kompilacji jest po prostu użytecznym sygnałem informującym, że możesz usunąć deklarację instancji. – Ben

5

(wiem, jestem późno do partii, ale to może być nadal być przydatna dla innych)

Można zachować instancje osieroconych we własnym modułem, to jeśli ktoś importuje tego modułu jest to specjalnie, ponieważ muszą i mogą uniknąć importowania ich, jeśli powodują problemy.