Ucząc się Haskella, zwróciłem uwagę na jego klasę typów , która ma być wielkim wynalazkiem wywodzącym się od Haskella.
Jednak na stronie Wikipedii o klasie typu :
Programista definiuje klasę typów, określając zestaw nazw funkcji lub stałych wraz z odpowiadającymi im typami, które muszą istnieć dla każdego typu należącego do tej klasy.
Co wydaje mi się dość bliskie interfejsowi Java (cytując stronę Interfejs Wikipedii (Java) ):
Interfejs w języku programowania Java jest typem abstrakcyjnym używanym do określenia interfejsu (w ogólnym znaczeniu tego terminu), który muszą implementować klasy.
Te dwie rzeczy wyglądają raczej podobnie: klasa typu ogranicza zachowanie typu, podczas gdy interfejs ogranicza zachowanie klasy.
Zastanawiam się, jakie są różnice i podobieństwa między klasą typów w Haskellu a interfejsem w Javie, a może są one zasadniczo różne?
EDYCJA: Zauważyłem, że nawet haskell.org przyznaje, że są podobne . Jeśli są tak podobne (a może są?), To dlaczego klasa typu jest traktowana takim szumem?
WIĘCEJ EDYCJI: Wow, tyle świetnych odpowiedzi! Myślę, że będę musiał pozwolić społeczności zdecydować, która z nich jest najlepsza. Jednak czytając odpowiedzi, wszyscy wydają się po prostu mówić, że „istnieje wiele rzeczy, które typeklasa może zrobić, podczas gdy interfejs nie może lub musi radzić sobie z typami ogólnymi” . Nie mogę pomóc, ale zastanawiam się, czy jest coś , co interfejsy mogą zrobić, a typeklasy nie? Zauważyłem również, że Wikipedia twierdzi, że typeklasa została pierwotnie wynaleziona w artykule z 1989 r. * „Jak sprawić, by polimorfizm ad-hoc był mniej ad hoc”, podczas gdy Haskell wciąż jest w swojej kolebce, podczas gdy projekt Java został uruchomiony w 1991 roku i po raz pierwszy wydany w 1995 roku Więc może zamiast typeklasy podobnej do interfejsów, jest odwrotnie, że na interfejsy miała wpływ typeklasa?Czy są jakieś dokumenty / dokumenty potwierdzające lub obalające to? Dzięki za wszystkie odpowiedzi, wszystkie są bardzo pouczające!
Dzięki za wszystkie uwagi!
Odpowiedzi:
Powiedziałbym, że interfejs jest trochę podobny do klasy typu, w
SomeInterface t
której wszystkie wartości mają typt -> whatever
(gdziewhatever
nie zawierat
). Dzieje się tak, ponieważ w przypadku rodzaju relacji dziedziczenia w Javie i podobnych językach wywoływana metoda zależy od typu obiektu, do którego są wywoływane, i od niczego innego.Oznacza to, że naprawdę trudno jest stworzyć takie rzeczy jak
add :: t -> t -> t
w przypadku interfejsu, w którym jest on polimorficzny dla więcej niż jednego parametru, ponieważ nie ma możliwości, aby interfejs określił, że typ argumentu i typ zwracany metody są tego samego typu co typ obiekt, do którego jest wywoływany (tj. typ „self”). W przypadku Generics istnieją sposoby na sfałszowanie tego, tworząc interfejs z parametrem ogólnym, który powinien być tego samego typu co sam obiekt, na przykład jakComparable<T>
to robi, gdzie oczekuje się użycia,Foo implements Comparable<Foo>
tak abycompareTo(T otherobject)
rodzaj miał typt -> t -> Ordering
. Ale to nadal wymaga od programisty przestrzegania tej zasady, a także powoduje bóle głowy, gdy ludzie chcą stworzyć funkcję korzystającą z tego interfejsu, muszą mieć rekurencyjne parametry typu ogólnego.Ponadto nie będziesz mieć takich rzeczy, jak
empty :: t
to, że nie wywołujesz tutaj funkcji, więc nie jest to metoda.źródło
Podobieństwo między interfejsami i klasami typów polega na tym, że nazywają i opisują zestaw powiązanych operacji. Same operacje są opisane za pomocą ich nazw, wejść i wyjść. Podobnie może istnieć wiele implementacji tych operacji, które prawdopodobnie będą się różniły.
Mając to na uwadze, oto kilka istotnych różnic:
Generalnie myślę, że można uczciwie powiedzieć, że klasy typów są bardziej wydajne i elastyczne niż interfejsy. Jak zdefiniowałbyś interfejs do konwersji ciągu znaków na jakąś wartość lub instancję typu implementującego? Z pewnością nie jest to niemożliwe, ale wynik nie byłby intuicyjny ani elegancki. Czy kiedykolwiek marzyłeś o możliwości zaimplementowania interfejsu dla typu w jakiejś skompilowanej bibliotece? Oba są łatwe do wykonania za pomocą klas typów.
źródło
Klasy typów zostały utworzone jako ustrukturyzowany sposób wyrażenia „polimorfizmu ad hoc”, który jest w zasadzie terminem technicznym określającym przeciążone funkcje . Definicja klasy typu wygląda mniej więcej tak:
Oznacza to, że kiedy używasz funkcji Apply
foo
do niektórych argumentów typu, które należą do klasyFoobar
, wyszukuje ona implementacjęfoo
specyficzną dla tego typu i używa jej. Jest to bardzo podobne do sytuacji z przeciążeniem operatorów w językach takich jak C ++ / C #, z wyjątkiem bardziej elastycznych i uogólnionych.Interfejsy służą podobnemu celowi w językach OO, ale podstawowa koncepcja jest nieco inna; Języki OO mają wbudowane pojęcie hierarchii typów, których Haskell po prostu nie ma, co w pewien sposób komplikuje sprawę, ponieważ interfejsy mogą obejmować zarówno przeciążanie przez podtypy (tj. Wywoływanie metod w odpowiednich instancjach, podtypy implementujące interfejsy ich nadtypów) i przez płaskie rozsyłanie oparte na typach (ponieważ dwie klasy implementujące interfejs mogą nie mieć wspólnej nadklasy, która również ją implementuje). Biorąc pod uwagę ogromną dodatkową złożoność wprowadzoną przez podtypy, sugeruję, że bardziej pomocne jest myślenie o klasach typów jako ulepszonej wersji przeciążonych funkcji w języku innym niż OO.
Warto również zauważyć, że klasy typów mają znacznie bardziej elastyczne sposoby rozsyłania - interfejsy zazwyczaj dotyczą tylko pojedynczej klasy, która ją implementuje, podczas gdy klasy typów są definiowane dla typu , który może pojawić się w dowolnym miejscu w sygnaturze funkcji klasy. Odpowiednikiem tego w interfejsach OO byłoby umożliwienie interfejsowi definiowania sposobów przekazywania obiektu tej klasy do innych klas, definiowania statycznych metod i konstruktorów, które wybierałyby implementację na podstawie tego, jaki typ zwracania jest wymagany w kontekście wywoływania, definiowania metod, które przyjmować argumenty tego samego typu, co klasa implementująca interfejs i różne inne rzeczy, które tak naprawdę nie tłumaczą.
Krótko mówiąc: służą podobnym celom, ale sposób ich działania jest nieco inny, a klasy typów są zarówno znacznie bardziej wyraziste, jak i, w niektórych przypadkach, prostsze w użyciu, ponieważ pracują nad stałymi typami, a nie elementami hierarchii dziedziczenia.
źródło
Przeczytałem powyższe odpowiedzi. Czuję, że mogę odpowiedzieć nieco jaśniej:
„Klasa typu” Haskella i „interfejs” Java / C # lub „cecha” Scali są w zasadzie analogiczne. Nie ma między nimi rozróżnienia koncepcyjnego, ale istnieją różnice w implementacji:
źródło
W Master minds of Programming jest wywiad o Haskellu z Philem Wadlerem, wynalazcą klas typów, który wyjaśnia podobieństwa między interfejsami w Javie i klasami typów w Haskell:
Tak więc klasy typów są powiązane z interfejsami, ale rzeczywista zgodność byłaby metodą statyczną sparametryzowaną typem jak powyżej.
źródło
Obejrzyj wykład Phillipa Wadlera Faith, Evolution, and Programming Languages . Wadler pracował nad Haskellem i był głównym współautorem Java Generics.
źródło
Przeczytaj artykuł dotyczący rozszerzenia oprogramowania i integracji z klasami typów, gdzie podano przykłady, w jaki sposób klasy typów mogą rozwiązać wiele problemów, których interfejsy nie mogą.
Przykłady wymienione w artykule to:
źródło
Nie mogę mówić na poziomie „szumu”, jeśli wydaje mi się to w porządku. Ale klasy typu tak są podobne pod wieloma względami. Jedyną różnicą, o której przychodzi mi do głowy, jest to, że Haskell możesz zapewnić zachowanie niektórych operacji klasy typu :
co pokazuje, że istnieją dwie operacje równe
(==)
i nierówne(/=)
dla rzeczy, które są instancjamiEq
klasy typu. Ale operacja nierówna jest definiowana w kategoriach równych (tak, że musisz podać tylko jedną) i odwrotnie.Więc w prawdopodobnie-nie-legalnej-Javie byłoby to coś takiego:
a sposób, w jaki to działałoby, polega na tym, że wystarczy podać jedną z tych metod, aby zaimplementować interfejs. Powiedziałbym więc, że możliwość zapewnienia pewnego rodzaju częściowej implementacji pożądanego zachowania na poziomie interfejsu jest różnicą.
źródło
Są podobne (czytaj: mają podobne zastosowanie) i prawdopodobnie zaimplementowane w podobny sposób: funkcje polimorficzne w Haskell biorą pod maskę „tabelę vtable” zawierającą funkcje powiązane z typeklasą.
Tę tabelę można często wywnioskować w czasie kompilacji. Jest to prawdopodobnie mniej prawdziwe w Javie.
Ale to jest tabela funkcji , a nie metod . Metody są powiązane z obiektem, typeklasy Haskella nie.
Zobacz je raczej jak typy generyczne Javy.
źródło
Jak mówi Daniel, implementacje interfejsów są definiowane oddzielnie od deklaracji danych. Jak zauważyli inni, istnieje prosty sposób definiowania operacji używających tego samego dowolnego typu w więcej niż jednym miejscu. Więc jest to łatwe do zdefiniowania
Num
jako typeklasę. W ten sposób w Haskell otrzymujemy składniowe korzyści wynikające z przeciążenia operatorów bez faktycznego posiadania żadnych magicznych operatorów przeciążonych - tylko standardowe typeklasy.Inną różnicą jest to, że możesz używać metod opartych na typie, nawet jeśli nie masz jeszcze konkretnej wartości tego typu!
Na przykład
read :: Read a => String -> a
. Więc jeśli masz wystarczająco dużo innych informacji na temat tego, jak użyjesz wyniku „odczytu”, możesz pozwolić kompilatorowi dowiedzieć się, którego słownika użyć za Ciebie.Możesz także zrobić takie rzeczy, jak to
instance (Read a) => Read [a] where...
pozwala zdefiniować wystąpienie odczytu dla dowolnego pliku listy czytelnych rzeczy. Myślę, że nie jest to całkiem możliwe w Javie.A wszystko to to tylko standardowe typeklasy jednoparametrowe, bez żadnych sztuczek. Gdy wprowadzimy wieloparametrowe typeklasy, otwiera się zupełnie nowy świat możliwości, a tym bardziej z zależnościami funkcjonalnymi i rodzinami typów, które pozwalają osadzić znacznie więcej informacji i obliczeń w systemie typów.
źródło