Niektóre języki dopuszczają klasy i funkcje z parametrami typu (np. List<T>
Gdzie T
moż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.
Functor
przykład w odpowiedzi Luisa Casillasa jest dość intuicyjny. Co mająList<T>
iOption<T>
mają wspólnego? Jeśli dasz mi jedną i jedną funkcjęT -> S
, mogę ci daćList<S>
lubOption<S>
. Kolejną wspólną cechą jest to, że możesz spróbować uzyskaćT
wartość z obu.IReadableHolder<T>
.IMappable<K<_>, T>
metodąK<S> Map(Func<T, S> f)
, wykonawczeIMappable<Option<_>, T>
,IMappable<List<_>, T>
. Musiałbyś więc ograniczyć się,K<T> : IMappable<K<_>, T>
aby z tego skorzystać.Odpowiedzi:
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, coT
wybierzesz. 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. TylkoList<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<>
iList<>
„mapować”, w tym sensie, że jeśli masz funkcję, możesz przekonwertowaćOption<T>
na anOption<S>
lubList<T>
naList<S>
. Pamiętając, że klasy nie mogą być używane do abstrakcyjnego nad typami wyższego rodzaju, zamiast tego tworzymy interfejs:A następnie zaimplementować interfejs zarówno
List<T>
aOption<T>
jakIMappable<List<_>, T>
iIMappable<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>
iList<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,
Jest to ograniczenie nakładane bezpośrednio na (nieokreślony) typ,
mp
który przyjmuje jeden parametr typu i wymaga, aby był on powiązany z funkcją,map
która zmienia anmp<a>
wmp<b>
. Następnie możemy napisać funkcje ograniczające typy wyższego rzędu,Mappable
tak 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.
źródło
(Mappable mp) => mp a -> mp b
, nałożyłeś ograniczenie namp
bycie członkiem klasy typuMappable
. Gdy deklarujesz typ,Option
któ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.*
nie czyniąc ich bezużytecznymi. Zdecydowanie jest jednak prawdą, że klasy typów są bardzo wydajne podczas pracy z typami wyższego rodzaju.