Dlaczego w Go nie ma leków generycznych?

126

Zastrzeżenie: grałem w Go tylko przez jeden dzień, więc jest duża szansa, że ​​wiele przegapiłem.

Czy ktoś wie, dlaczego w Go nie ma prawdziwego wsparcia dla typów ogólnych / szablonów / whatsInAName? Jest więc rodzaj ogólny map, który jest dostarczany przez kompilator, podczas gdy programista Go nie może napisać własnej implementacji. Biorąc pod uwagę całą rozmowę o uczynieniu Go tak ortogonalnym, jak to tylko możliwe, dlaczego mogę UŻYWAĆ typu ogólnego, ale nie STWORZYĆ nowego?

Zwłaszcza jeśli chodzi o programowanie funkcjonalne, istnieją lambdy, a nawet domknięcia, ale w statycznym systemie typów brakuje typów ogólnych, jak napisać, no cóż, ogólne funkcje wyższego rzędu, takie jak filter(predicate, list)? OK, powiązane listy i tym podobne można zrobić, interface{}poświęcając bezpieczeństwo typów.

Ponieważ szybkie wyszukiwanie w SO / Google nie ujawniło żadnych spostrzeżeń, wygląda na to, że generics, jeśli w ogóle, zostaną dodane do Go po namyśle. Ufam, że Thompson radzi sobie znacznie lepiej niż ludzie z Java, ale po co trzymać z daleka generyczne? A może są zaplanowane i jeszcze nie wdrożone?

s4y
źródło
Myślę, że warto na to zwrócić uwagę: używanie interfejsu {} nie rezygnuje z bezpieczeństwa typów. Jest to typ i można go potwierdzić (nie rzutować) na inne typy, ale te potwierdzenia nadal wywołują sprawdzenia w czasie wykonywania, aby zachować bezpieczeństwo typów.
cthom06
12
interface{}poświęca bezpieczeństwo typu statycznego . Jednak jest to nieco dziwna skarga, gdy wspomina się o Schemacie w następnym akapicie, ponieważ Schemat zwykle nie ma statycznego sprawdzania typu.
poolie
@poolie: To, co mnie niepokoi, to trzymanie się JEDNEGO paradygmatu w języku. Albo nie używam statycznego bezpieczeństwa typu XOR.
2
przy okazji jest napisane jako „Go”, a nie „GO”, jak można zobaczyć na golang.org. Rozróżniana jest wielkość liter. :-)
poolie

Odpowiedzi:

78

tę odpowiedź znajdziesz tutaj: http://golang.org/doc/faq#generics

Dlaczego Go nie ma typów ogólnych?

W pewnym momencie można dodać leki generyczne. Nie odczuwamy dla nich pilności, chociaż rozumiemy, że niektórzy programiści tak robią.

Typy generyczne są wygodne, ale wiążą się ze złożonością systemu typów i czasu wykonywania. Nie znaleźliśmy jeszcze projektu, który nadaje wartość proporcjonalną do złożoności, chociaż nadal o tym myślimy. Tymczasem wbudowane mapy i wycinki w Go, a także możliwość używania pustego interfejsu do konstruowania kontenerów (z jawnym rozpakowywaniem) oznaczają, że w wielu przypadkach można napisać kod, który robi to, co generyczne umożliwiłyby, jeśli mniej płynnie.

To pozostaje otwarta kwestia.

Vinzenz
źródło
14
@amoebe, „pusty interfejs”, zapisany w formie pisowni interface{}, jest najbardziej podstawowym typem interfejsu i każdy obiekt go zapewnia. Jeśli utworzysz pojemnik, który je zawiera, może przyjąć dowolny (nieprymitywny) obiekt. Jest więc bardzo podobny do kontenera Objectsw Javie.
bilard
4
@YinWang Generics nie są tak proste w środowisku wywnioskowanym o typie. Co ważniejsze; interface {} nie jest odpowiednikiem wskaźników void * w C. Lepszymi analogiami byłyby typy System.Object w języku C # lub identyfikatory celu-C. Informacje o typie są zachowywane i mogą być „rzutowane” (faktycznie) z powrotem do konkretnego typu. Uzyskaj szczegółowe informacje tutaj: golang.org/ref/spec#Type_assertions
tbone
2
@tbone C #'s System.Object (lub obiekt Java jako taki) jest zasadniczo tym, co miałem na myśli, mówiąc o "void pointers C" (ignorując część, w której nie można wykonywać arytmetyki wskaźników w tych językach). To tam gubią się statyczne informacje o typie. Rzut niewiele pomoże, ponieważ wystąpi błąd wykonania.
Ian
1
Szablony @ChristopherPfohl D wydają się mieć nieco mniej narzutu czasu kompilacji i zwykle nie generujesz więcej kodu za pomocą szablonów niż normalnie byś zrobił w przeciwnym razie (w rzeczywistości możesz skończyć z mniejszą ilością kodu w zależności od okoliczności).
Sześcienny
3
@ChristopherPfohl Myślę, że tylko generyczne programy Java mają problem z pudełkiem / rozpakowywaniem dla typów prymitywnych? C # reified rodzajowy nie ma problemu.
ca9163d9
32

Idź 2

Projekt projektu leków generycznych jest dostępny pod adresem https://blog.golang.org/go2draft .

Idź 1

Russ Cox, jeden z weteranów Go, napisał na blogu post zatytułowany The Generic Dilemma , w którym pyta

… Czy potrzebujesz wolnych programistów, wolnych kompilatorów i rozdętych plików binarnych, czy też wolnych czasów wykonywania?

Powolni programiści są wynikiem braku typów ogólnych, powolne kompilatory są spowodowane przez C ++, podobnie jak typy generyczne, a długie czasy wykonywania wynikają z podejścia typu boxing-unboxing, którego używa Java.

Czwarta możliwość niewymieniona na blogu to droga C #. Generowanie wyspecjalizowanego kodu jak w C ++, ale w czasie wykonywania, kiedy jest to potrzebne. Bardzo mi się podoba, ale Go bardzo różni się od C #, więc prawdopodobnie w ogóle nie ma zastosowania…

Powinienem wspomnieć, że używanie popularnej techniki programowania podobnego do Java 1.4 w ruchu, która rzutuje na, interface{}cierpi na dokładnie te same problemy, co boxing-unboxing (ponieważ to właśnie robimy), oprócz utraty bezpieczeństwa typu kompilacji. Dla małych typów (takich jak ints) Go optymalizuje interface{}typ tak, że lista int, które zostały rzutowane na interfejs {} zajmuje ciągły obszar pamięci i zajmuje tylko dwa razy więcej miejsca niż normalne wartości int. interface{}Jednak nadal istnieje narzut związany z sprawdzaniem środowiska uruchomieniowego podczas przesyłania z . Odniesienie .

Wszystkie projekty, które dodają obsługę standardową (jest ich kilka i wszystkie są interesujące) jednolicie przechodzą na ścieżkę C ++ generowania kodu czasu kompilacji.

user7610
źródło
Moim rozwiązaniem tego dylematu byłoby ustawienie domyślne opcji Go na „wolne czasy wykonywania” z opcją profilowania programu i rekompilacji najbardziej wrażliwych na wydajność części w trybie „powolnych kompilatorów i rozdętych plików binarnych”. Szkoda, że ​​ludzie faktycznie implementujący takie rzeczy mają tendencję do korzystania z trasy C ++.
user7610
1
Wspomniano, że małe typy (tj. Int), które są przechowywane, []interface{}używają 2x więcej pamięci RAM niż []int. Chociaż prawda, nawet mniejsze typy (tj. Bajty) wykorzystują do 16 razy więcej pamięci RAM niż pliki []byte.
BMiner
Właściwie nie ma dylematu z podejściem C ++. Jeśli programista zdecyduje się napisać kod szablonu, korzyści z tego muszą przewyższyć koszt powolnej kompilacji. W przeciwnym razie mógłby to zrobić po prostu w stary sposób.
John Z. Li
Dylemat dotyczy tego, które podejście wybrać. Jeśli rozwiążesz ten dylemat, przechodząc do podejścia C ++, dylemat zostanie rozwiązany.
user7610
9

Mimo że generyczne nie są obecnie wbudowane, istnieje kilka zewnętrznych implementacji typów generycznych dla go, które używają komentarzy w połączeniu z małymi narzędziami generującymi kod.

Oto jedna taka implementacja: http://clipperhouse.github.io/gen/

Alexander
źródło
1

Właściwie zgodnie z tym postem:

Wiele osób doszło do wniosku (niepoprawnie), że stanowisko zespołu Go brzmi: „Go nigdy nie będzie mieć leków generycznych”. Wręcz przeciwnie, rozumiemy, że istnieją potencjalne leki generyczne, zarówno po to, aby uczynić Go znacznie bardziej elastycznym i wydajnym, jak i znacznie bardziej skomplikować Go. Jeśli mamy dodać typy generyczne, chcemy to zrobić w sposób, który zapewnia jak największą elastyczność i moc przy jak najmniejszej dodatkowej złożoności.

Ayush Gupta
źródło
-1

W Go 2 rozważany jest polimorfizm parametryczny (generics) .

Takie podejście wprowadziłoby pojęcie kontraktu , którego można użyć do wyrażenia ograniczeń dotyczących parametrów typu:

contract Addable(a T) {
  a + a // Could be += also
}

Taka umowa mogłaby być następnie wykorzystana w ten sposób:

func Sum(type T Addable)(l []T) (total T) {
  for _, e := range l {
    total += e
  }
  return total
}

To propozycja na tym etapie.


Twoja filter(predicate, list)funkcja może zostać zaimplementowana z parametrem typu takim jak ten:

func Filter(type T)(f func(T) bool, l []T) (result []T) {
  for _, e := range l {
    if f(e) {
      result = append(result, e)
    }
  }
  return result
}

W takim przypadku nie ma potrzeby ograniczania T.

ᆼ ᆺ ᆼ
źródło
1
Jeśli czytasz dzisiaj tę odpowiedź, pamiętaj, że umowy zostały usunięte z projektu propozycji: go.googlesource.com/proposal/+/refs/heads/master/design/…
jub0bs