Dlaczego potrzebujesz wyższych rodzajów?


13

Niektóre języki dopuszczają klasy i funkcje z parametrami typu (np. List<T>Gdzie Tmoże być dowolnym typem). Na przykład możesz mieć funkcję:

List<S> Function<S, T>(List<T> list)

Niektóre języki umożliwiają jednak rozszerzenie tej koncepcji o jeden poziom wyżej, co pozwala na posiadanie funkcji z podpisem:

K<S> Function<K<_>, S, T>(K<T> arg)

Gdzie K<_>sam jest typem takim, List<_>który ma parametr typu. Ten „częściowy typ” jest znany jako konstruktor typów.

Moje pytanie brzmi: dlaczego potrzebujesz tej umiejętności? Sensowne jest mieć taki typ, List<T>ponieważ wszystkie List<T>są prawie dokładnie takie same, ale wszystkie K<_>mogą być zupełnie inne. Możesz mieć Option<_>i List<_>, które nie mają żadnej wspólnej funkcjonalności.


7
Istnieje kilka dobrych odpowiedzi tutaj: stackoverflow.com/questions/21170493/…
— itsbruce

3
@itsbruce W szczególności Functorprzykład w odpowiedzi Luisa Casillasa jest dość intuicyjny. Co mają List<T>i Option<T>mają wspólnego? Jeśli dasz mi jedną i jedną funkcję T -> S, mogę ci dać List<S>lub Option<S>. Kolejną wspólną cechą jest to, że możesz spróbować uzyskać Twartość z obu.
— Doval,

@Doval: Jak zrobiłbyś to pierwsze? W stopniu, w jakim interesuje się tym drugim, sądzę, że można to rozwiązać poprzez wdrożenie obu typów IReadableHolder<T>.
— supercat

@supercat Zgaduję musieliby realizować IMappable<K<_>, T>metodą K<S> Map(Func<T, S> f), wykonawcze IMappable<Option<_>, T>, IMappable<List<_>, T>. Musiałbyś więc ograniczyć się, K<T> : IMappable<K<_>, T>aby z tego skorzystać.
— GregRos

1
Przesyłanie nie jest odpowiednią analogią. Klasy typów (lub podobne konstrukcje) są wykonywane w taki sposób, aby pokazać, w jaki sposób dany typ spełnia typ wyższego typu. Zazwyczaj obejmuje to zdefiniowanie nowych funkcji lub pokazanie, które z istniejących funkcji (lub metod, jeśli jest to język OO, taki jak Scala), których można użyć. Po wykonaniu tej czynności wszystkie funkcje zdefiniowane do pracy z wyższym typem będą działać z tym typem. Ale to znacznie więcej niż interfejs, ponieważ zdefiniowano więcej niż prosty zestaw sygnatur funkcji / metod. Chyba będę musiał napisać odpowiedź, aby pokazać, jak to działa.
— itsbruce

Odpowiedzi:


5

Ponieważ nikt inny nie odpowiedział na to pytanie, myślę, że spróbuję. Będę musiał trochę filozofować.

Programowanie ogólne polega na abstrakcji nad podobnymi typami, bez utraty informacji o typie (co dzieje się z polimorfizmem wartości obiektowych). Aby to zrobić, typy muszą koniecznie współdzielić jakiś interfejs (zestaw operacji, a nie termin OO), którego można użyć.

W językach obiektowych typy spełniają interfejs dzięki klasom. Każda klasa ma własny interfejs zdefiniowany jako część swojego typu. Ponieważ wszystkie klasy List<T>mają ten sam interfejs, możesz pisać kod, który działa bez względu na to, co Twybierzesz. Innym sposobem nałożenia interfejsu jest ograniczenie dziedziczenia i chociaż oba wydają się różne, są one trochę podobne, jeśli się nad tym zastanowić.

W większości języków obiektowych List<>sam w sobie nie jest właściwym typem. Nie ma metod, a zatem nie ma interfejsu. Tylko List<T>to ma te rzeczy. Zasadniczo, z bardziej technicznego punktu widzenia, jedynymi typami, które można znacząco wyodrębnić, są te z tym rodzajem *. Aby korzystać z typów lepiej dobranych w świecie zorientowanym obiektowo, musisz sformułować ograniczenia typu w sposób zgodny z tym ograniczeniem.

Na przykład, jak wspomniano w komentarzach, możemy przeglądać Option<>i List<>„mapować”, w tym sensie, że jeśli masz funkcję, możesz przekonwertować Option<T>na an Option<S>lub List<T>na List<S>. Pamiętając, że klasy nie mogą być używane do abstrakcyjnego nad typami wyższego rodzaju, zamiast tego tworzymy interfejs:

IMappable<K<_>, T> where K<T> : IMappable<K<_>, T>

A następnie zaimplementować interfejs zarówno List<T>a Option<T>jak IMappable<List<_>, T>i IMappable<Option<_>, T>odpowiednio. To, co zrobiliśmy, polega na użyciu typów o wyższym rodzaju, aby nałożyć ograniczenia na rzeczywiste typy (o innym rodzaju) Option<T>i List<T>. Tak to się dzieje w Scali, chociaż oczywiście Scala ma takie cechy, jak cechy, zmienne typu i ukryte parametry, które czynią ją bardziej wyrazistą.

W innych językach można bezpośrednio wyodrębnić typy wyższego rodzaju. W Haskell, jednym z najwyższych uprawnień w systemach typów, możemy sformułować klasę typu dla dowolnego typu, nawet jeśli ma on wyższy rodzaj. Na przykład,

class Mappable mp where
    map :: mp a -> mp b

Jest to ograniczenie nakładane bezpośrednio na (nieokreślony) typ, mpktóry przyjmuje jeden parametr typu i wymaga, aby był on powiązany z funkcją, mapktóra zmienia an mp<a>w mp<b>. Następnie możemy napisać funkcje ograniczające typy wyższego rzędu, Mappabletak jak w językach zorientowanych obiektowo można wprowadzić ograniczenie dziedziczenia. Cóż, w pewnym sensie.

Podsumowując, twoja umiejętność korzystania z typów o wyższym rodzaju zależy od twojej zdolności do ich ograniczenia lub użycia ich jako części ograniczeń typów.


Byłem zbyt zajęty zmianą pracy, ale w końcu znajdę trochę czasu na napisanie własnej odpowiedzi. Myślę jednak, że rozproszyła Cię idea ograniczeń. Jednym z istotnych aspektów wyższych rodzajów rodzajów jest to, że pozwalają one na zdefiniowanie funkcji / zachowania, które można zastosować do dowolnego typu, który logicznie można wykazać, że się kwalifikuje. Daleki od nakładania ograniczeń na istniejące typy, klasy typów (jedna aplikacja wyższych typów) dodają do nich zachowanie.
— itsbruce

Myślę, że to kwestia semantyki. Klasa typu definiuje klasę typów. Kiedy definiujesz funkcję taką jak (Mappable mp) => mp a -> mp b, nałożyłeś ograniczenie na mpbycie członkiem klasy typu Mappable. Gdy deklarujesz typ, Optionktóry ma być instancją Mappable, dodajesz zachowanie do tego typu. Sądzę, że możesz skorzystać z tego zachowania lokalnie, nie ograniczając żadnego typu, ale nie różni się to od definiowania zwykłej funkcji.
— GregRos

Ponadto jestem pewien, że klasy typów nie są bezpośrednio związane z typami wyższego rodzaju. Scala ma typy lepiej dobrane, ale nie klasy, i można ograniczyć klasy do typów z rodzajem, *nie czyniąc ich bezużytecznymi. Zdecydowanie jest jednak prawdą, że klasy typów są bardzo wydajne podczas pracy z typami wyższego rodzaju.
— GregRos

Scala ma klasy typów. Są wdrażane w sposób dorozumiany. Implikacje zapewniają odpowiednik instancji klasy typu Haskell. Jest to bardziej delikatna implementacja niż Haskell, ponieważ nie ma dedykowanej składni. Ale zawsze był to jeden z kluczowych celów implikacji Scali i wykonują swoją pracę.
— itsbruce
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.