Wątek przeniesiony 2021-09-16 09:52 z Inne języki programowania przez cerrato.

Go - wasze doświadczenia z tym językiem.

0

Dzień dobry. Chętnie zapoznam się z waszym zdaniem na temat tego języka. W wielu artykułach można przeczytać z jego zaletach, między innymi szybkim kompilatorze, silnym typowaniu, nowej konstrukcji obiektowośći, która nie zawiera klas. Jak język go spisuje się w web-devie oraz na innych płaszczyznach?

7

Język idealny na /r/PCJ.

Sam język ma sporo wad:

  • Domyślny manager pakietów, który nie nadaje się do zarządzania pakietami, bo nie posiada wersjonowania. W związku z czym musisz się posiłkować zewnętrznymi rozwiązaniami.
  • Nie wiem jak, ale w XXI w. ktoś dalej myśli, że nil/null to dobry pomysł?
  • A propos powyższego: if err != nil { … } WSZĘDZIE.
  • Brak typów generycznych zdefiniowanych przez użytkownika, co oznacza, że albo masz kopiuj-wklej (przykładowo funkcja min nie istnieje w Go), typy generyczne są dostępne tylko dla kompilatora (mapy i kanały). Przez co to silne typowanie staje się mniej silne.
  • Mi osobiście nie podchodzi styl interfejsów w Go, czyt. wszystko co implementuje metodę foo od razu implementuje interfejs Foo, co niekoniecznie musi być prawdą.
0

Bardzo dziękuję odpowiedź.

0

Uczyłem się Go tak po godzinach dla zwykłej ciekawości i rzeczywiście, sam język nie jest doskonały, ale też ma plusy o których rzadziej się wspomina:

  1. Język jest banalny i zdecydowanie wymaga mniejszego zaangażowania niż clojure/elixir. Jeśli znasz C++/C#/Java bądź nawet Pythona to załapiesz go parę dni, tu nie musisz przestawiać się na programowanie funkcyjne / niemodyfikowalne dane itp (ps, to że nie musisz nie znaczy, że będzie lepiej :D)

  2. Pozwala programiście decydować czy wybrane fragmenty kodu odpalamy synchronicznie czy asynchronicznie. To ułatwia programowanie współbieżych rzeczy. Dla przykładu NodeJS wymusza programowanie asynchronicznie, a taki Python głównie synchronicznie. Natomiast mi najlepiej się programuje, gdy samemu mogę decydować jak coś odpalić. Oczywiście w obu wspomnianych językach da się tworzyć projekty oparte np. na websocketach, ale zazwyczaj wymaga to więcej wprawy i doświadczenia. Natomiast taki Go ma to już wmontowane na sam start!

Go mógłby świetnie sprawdzić się w kodowaniu backendu dla gier online lub tworzeniu małych serwisów z nastawieniem na wydajne mielenie danych.

0

Mi nie przypasował, wolę Rust.

4

Sam język ma sporo wad:

A który ich nie ma?

  • Domyślny manager pakietów, który nie nadaje się do zarządzania pakietami, bo nie posiada wersjonowania. W związku z czym musisz się posiłkować zewnętrznymi rozwiązaniami.

Mniemam, że piszesz o 'go get' - on obsługuje różne rodzaje repozytoriów (m.in gita). Rozumiem, że w związku z tym, zarzucasz, że np. git nie ma wersjonowania? Bo ja mogę zrobić checkout dowolnego taga/commita i mam bardzo dokładnie to, co chcę. Wszystko w standardowej paczce.

  • Nie wiem jak, ale w XXI w. ktoś dalej myśli, że nil/null to dobry pomysł?

Ale zdajesz sobie sprawę po co to w ogóle wprowadzono?

  • A propos powyższego: if err != nil { … } WSZĘDZIE.

I co? Moim zdaniem lepsze to, niż czytać kod 5 minut, by zorientować się, że dopiero siedzimy w jakiejś sekcji try/catch, która ciągnie się od bazyliona linii. To zdecydowanie mi bardziej odpowiada - komu nie, niech używa innego języka.

  • Brak typów generycznych zdefiniowanych przez użytkownika, co oznacza, że albo masz kopiuj-wklej (przykładowo funkcja min nie istnieje w Go), typy generyczne są dostępne tylko dla kompilatora (mapy i kanały). Przez co to silne typowanie staje się mniej silne.

Dyskusja o generykach to standardowy przykład dyskusji o Go z innymi devami. Jest tyleż samo zwolenników i przeciwników. Padło już mnóstwo argumentów za i przeciw. Dla mnie osobiście kod bez generyków jest czytelniejszy i tyle, choć nie krytykuję tego w innych językach. W Go kod jest immutable i takie problemy spowodowane "ułomnością" tego języka, rozwiązujemy templejtami. Po prostu.

Ale może ja jakiś dziwny jestem bo, jak mi np. mi kolor czerwony nie pasuje, to nie kupuję sobie auta w kolorze czerwonym i tyle. I jak takiego auta nie mam, to też nie psioczę potem na mieście, że inni jeżdżą czerwonymi.

  • Mi osobiście nie podchodzi styl interfejsów w Go, czyt. wszystko co implementuje metodę foo od razu implementuje
    interfejs Foo, co niekoniecznie musi być prawdą.

No to masz Jave, tam masz "implements" i jesteś szczęśliwy (zakładam)

3
TurkucPodjadek napisał(a):

Sam język ma sporo wad:

A który ich nie ma?

Każdy język ma wady, ale częścią zawodu programisty jest wybieranie takiego narzędzia, które będzie miało ich najmniej w stosunku do celu jaki chcemy osiągnąć. Go niestety powiela wiele bezsensownych wad, których łatwo można było uniknąć i zdecydowanie poprawiły by jakość (i poprawność) pisanego kodu.

  • Domyślny manager pakietów, który nie nadaje się do zarządzania pakietami, bo nie posiada wersjonowania. W związku z czym musisz się posiłkować zewnętrznymi rozwiązaniami.

Mniemam, że piszesz o 'go get' - on obsługuje różne rodzaje repozytoriów (m.in gita). Rozumiem, że w związku z tym, zarzucasz, że np. git nie ma wersjonowania? Bo ja mogę zrobić checkout dowolnego taga/commita i mam bardzo dokładnie to, co chcę. Wszystko w standardowej paczce.

No właśnie nie. Możesz zrobić go get w projekcie, ale nie masz żadnej możliwości określenia, że konkretnie ta wersja ma być pobrana. Coś w stylu Gemfile czy innych, gdzie możesz określić (SemVer), które wersje są "działające" oraz Gemfile.lock, który określa które wersje mają być pobrane, dzięki czemu wszyscy developerzy używają tych samych wersji. Ba nawet problematyczne jest używanie tych różnych wersji pakietów między różnymi projektami, bo go get pobiera wszystkie zależności do $GO_PATH.

  • Nie wiem jak, ale w XXI w. ktoś dalej myśli, że nil/null to dobry pomysł?

Ale zdajesz sobie sprawę po co to w ogóle wprowadzono?

Bo twórca Algola był niedość asertywny względem samego siebie by tego nie wprowadzić.

  • A propos powyższego: if err != nil { … } WSZĘDZIE.

I co? Moim zdaniem lepsze to, niż czytać kod 5 minut, by zorientować się, że dopiero siedzimy w jakiejś sekcji try/catch, która ciągnie się od bazyliona linii. To zdecydowanie mi bardziej odpowiada - komu nie, niech używa innego języka.

To są zupełnie 2 różne sprawy. Od tego by nie pisać kodu na bazylion linii masz lintery. Dodatkowo są inne rozwiązania, które IMHO są zdecydowanie czytelniejsze, a dodatkowo załatwiają to i problem wyżej: tagged unions (polska Wiki tłumaczy na "rekord z wariantami). Jednak takie rozwiązanie wymaga:

  • Brak typów generycznych zdefiniowanych przez użytkownika, co oznacza, że albo masz kopiuj-wklej (przykładowo funkcja min nie istnieje w Go), typy generyczne są dostępne tylko dla kompilatora (mapy i kanały). Przez co to silne typowanie staje się mniej silne.

Dyskusja o generykach to standardowy przykład dyskusji o Go z innymi devami. Jest tyleż samo zwolenników i przeciwników. Padło już mnóstwo argumentów za i przeciw. Dla mnie osobiście kod bez generyków jest czytelniejszy i tyle, choć nie krytykuję tego w innych językach. W Go kod jest immutable i takie problemy spowodowane "ułomnością" tego języka, rozwiązujemy templejtami. Po prostu.

Czyli szablony są złe, więc będziemy generować kod podczas kompilacji z czegoś co nazwiemy "templejtami" i będziemy udawać, że nie potrzebujemy typów generycznych, bo mamy rozwiązanie (czyli typy generyczne). Uwielbiam ten argument. Przypomina mi się wtedy alfabet rdzennych ludów Kanady, cudowne rozwiązanie.

Poza tym powiem Ci, że (niesamowite) ale w zdecydowanej większości kompilowanych języków, kod jest immutable. Te twoje całe templejty to nic innego jak próba symulacji generyków i nazywanie tego "rozwiązaniem".

Ale może ja jakiś dziwny jestem bo, jak mi np. mi kolor czerwony nie pasuje, to nie kupuję sobie auta w kolorze czerwonym i tyle. I jak takiego auta nie mam, to też nie psioczę potem na mieście, że inni jeżdżą czerwonymi.

A jak chcesz znaleźć minimum 2 liczb to zawsze piszesz własną funkcję? Albo posortować kontener? Typy generyczne nie są do kodu używanego w twoim projekcie tylko przez ciebie, one są dla twórców bibliotek, by mogli tworzyć biblioteki działające dla wszystkich przypadków (np. kontenery).

  • Mi osobiście nie podchodzi styl interfejsów w Go, czyt. wszystko co implementuje metodę foo od razu implementuje
    interfejs Foo, co niekoniecznie musi być prawdą.

No to masz Jave, tam masz "implements" i jesteś szczęśliwy (zakładam)

Źle zakładasz. Obecnie piszę w języku, gdzie nie ma żadnego wymuszenia konkretnego interfesu (Elixir) i muszę przyznać, że często mi to przeszkadza.

Ale weźmy przykład z życia:

Chcemy zaimplementować funkcję sortującą nasz kontener (w końcu nie ma typów generycznych, więc musimy to zrobić samemu), więc mamy:

type Eq interface {
    eq(other Eq) bool // niestety nie jesteśmy w stanie na etapie kompilacji sprawdzić czy typeof(a) == typeof(b), smuteg :(
}

func Deduplicate(data *[]Eq) { … } // Dalej nie możemy mieć pewności, że wszystkie elementy tablicy są takie same

No i tutaj pojawia się nagle zonk, otóż ktoś gdzieś (przez przypadek, niewiedzę, cokolwiek) implementuje funkcję:

type x struct {
    a float64
}

func (a  x) eq(b Eq) bool {
    return a.a == b.a
}

No i nasza funkcja przestaje działać prawidłowo.

Wynika z tego, że albo musimy wykazywać odpowiednią kreatywność w wymyślaniu metod dla naszych struktur albo liczyć się z możliwymi problemami w przyszłości. Języki, w których metody są powiązane z interfejsem nie mają takich problemów, przykład:

trait Eq: PartialEq<Self> {}

Gdzie możemy użyć metody z interfejsu wyżej bez żadnych problemów z pewnością na poziomie ufności co do autora biblioteki, że nam się takowa funkcja nie wyłoży nigdzie po drodze.

1
hauleth napisał(a):
TurkucPodjadek napisał(a):

Sam język ma sporo wad:

A który ich nie ma?

Każdy język ma wady, ale częścią zawodu programisty jest wybieranie takiego narzędzia, które będzie miało ich najmniej w stosunku do celu jaki chcemy osiągnąć. Go niestety powiela wiele bezsensownych wad, których łatwo można było uniknąć i zdecydowanie poprawiły by jakość (i poprawność) pisanego kodu.

To można o każdym języku napisać. Ba, powiem więcej - stwórz swój doskonały język, a gwarantuje Ci, że zaraz znajdzie się 2^12 programistów, co stwierdzi, że

  1. Tego i tego w nim brakuje
  2. To i to w nim źle rozwiązano
  3. Ogólnie zmarnowany potencjał
  4. W ogóle to po co powstał?
  • Domyślny manager pakietów, który nie nadaje się do zarządzania pakietami, bo nie posiada wersjonowania. W związku z czym musisz się posiłkować zewnętrznymi rozwiązaniami.

Mniemam, że piszesz o 'go get' - on obsługuje różne rodzaje repozytoriów (m.in gita). Rozumiem, że w związku z tym, zarzucasz, że np. git nie ma wersjonowania? Bo ja mogę zrobić checkout dowolnego taga/commita i mam bardzo dokładnie to, co chcę. Wszystko w standardowej paczce.

No właśnie nie. Możesz zrobić go get w projekcie, ale nie masz żadnej możliwości określenia, że konkretnie ta wersja ma być pobrana. Coś w stylu Gemfile czy innych, gdzie możesz określić (SemVer), które wersje są "działające" oraz Gemfile.lock, który określa które wersje mają być pobrane, dzięki czemu wszyscy developerzy używają tych samych wersji. Ba nawet problematyczne jest używanie tych różnych wersji pakietów między różnymi projektami, bo go get pobiera wszystkie zależności do $GO_PATH.

O gicie (go get) i go deps pewnie jednak nie słyszałeś? Czyli do tej pory, niby źle robiłem, że mi to działało? Przypominam: w gicie jest wersjonowanie.

  • Nie wiem jak, ale w XXI w. ktoś dalej myśli, że nil/null to dobry pomysł?

Ale zdajesz sobie sprawę po co to w ogóle wprowadzono?

Bo twórca Algola był niedość asertywny względem samego siebie by tego nie wprowadzić.

  • A propos powyższego: if err != nil { … } WSZĘDZIE.

I co? Moim zdaniem lepsze to, niż czytać kod 5 minut, by zorientować się, że dopiero siedzimy w jakiejś sekcji try/catch, która ciągnie się od bazyliona linii. To zdecydowanie mi bardziej odpowiada - komu nie, niech używa innego języka.

To są zupełnie 2 różne sprawy. Od tego by nie pisać kodu na bazylion linii masz lintery. Dodatkowo są inne rozwiązania, które IMHO są zdecydowanie czytelniejsze, a dodatkowo załatwiają to i problem wyżej: tagged unions (polska Wiki tłumaczy na "rekord z wariantami). Jednak takie rozwiązanie wymaga:

IMHO jest bardzo naukowym argumentem jak widzisz. Do tego nie do tego piłem, error checking w języku nie powinny rozwiązywać żadne zewnętrzne lintery! IMO error checking w Go, to co zarzuciłeś wyżej, jest OK. Wchodzę w byle jaki kod i od razu wiem co to robi bez orientowania się, w której sekcji jestem. Ja to lubię dla odmiany, co teraz? Kto ma rację?

  • Brak typów generycznych zdefiniowanych przez użytkownika, co oznacza, że albo masz kopiuj-wklej (przykładowo funkcja min nie istnieje w Go), typy generyczne są dostępne tylko dla kompilatora (mapy i kanały). Przez co to silne typowanie staje się mniej silne.

Dyskusja o generykach to standardowy przykład dyskusji o Go z innymi devami. Jest tyleż samo zwolenników i przeciwników. Padło już mnóstwo argumentów za i przeciw. Dla mnie osobiście kod bez generyków jest czytelniejszy i tyle, choć nie krytykuję tego w innych językach. W Go kod jest immutable i takie problemy spowodowane "ułomnością" tego języka, rozwiązujemy templejtami. Po prostu.

Czyli szablony są złe, więc będziemy generować kod podczas kompilacji z czegoś co nazwiemy "templejtami" i będziemy udawać, że nie potrzebujemy typów generycznych, bo mamy rozwiązanie (czyli typy generyczne). Uwielbiam ten argument. Przypomina mi się wtedy alfabet rdzennych ludów Kanady, cudowne rozwiązanie.

Poza tym powiem Ci, że (niesamowite) ale w zdecydowanej większości kompilowanych języków, kod jest immutable. Te twoje całe templejty to nic innego jak próba symulacji generyków i nazywanie tego "rozwiązaniem".

Nie ma generics - nie używasz, no proste. Twórcy języka tak chcieli i jest. Ponieważ jednak coraz więcej softu powstaje w Go (https://awesome-go.com polecam), to jednak chyba te Twoje problemy nie są tak istotne, nie? Albo inaczej: stwórz własny język - patrz pierwsza odpowiedź.

PS Jest oczywiście interface{}, jako taka "proteza", ale ja nie polecam go używać, dlatego o tym nie wspominam w dyskusji.

Ale może ja jakiś dziwny jestem bo, jak mi np. mi kolor czerwony nie pasuje, to nie kupuję sobie auta w kolorze czerwonym i tyle. I jak takiego auta nie mam, to też nie psioczę potem na mieście, że inni jeżdżą czerwonymi.

A jak chcesz znaleźć minimum 2 liczb to zawsze piszesz własną funkcję? Albo posortować kontener? Typy generyczne nie są do kodu używanego w twoim projekcie tylko przez ciebie, one są dla twórców bibliotek, by mogli tworzyć biblioteki działające dla wszystkich przypadków (np. kontenery).

I znowu dochodzimy do IMHO, twórcy tak uznali i tyle. Źle? To pisz w języku, gdzie masz funkcje min(), nie rozumiem zarzutu? Używając danego języka akceptujesz jego pradygmaty, proste. Jak Ci nie pasują - to go nie używasz. Jest też metoda pośrednia - używaj dobrego języka do danego zastosowania nawet mieszając je między sobą w obrębie jednego projektu, jeśli to możliwe. Bo każdemu językowi można zarzucić, że język A nie ma tego, a język B to ma (i odwrotnie)

  • Mi osobiście nie podchodzi styl interfejsów w Go, czyt. wszystko co implementuje metodę foo od razu implementuje
    interfejs Foo, co niekoniecznie musi być prawdą.

No to masz Jave, tam masz "implements" i jesteś szczęśliwy (zakładam)

Źle zakładasz. Obecnie piszę w języku, gdzie nie ma żadnego wymuszenia konkretnego interfesu (Elixir) i muszę przyznać, że często mi to przeszkadza.

Wiesz, jak mnie praca przeszkadza, to ją zmieniam, ale podobno ja dziwny jestem...

Ale weźmy przykład z życia:

Chcemy zaimplementować funkcję sortującą nasz kontener (w końcu nie ma typów generycznych, więc musimy to zrobić samemu), więc mamy:

type Eq interface {
    eq(other Eq) bool // niestety nie jesteśmy w stanie na etapie kompilacji sprawdzić czy typeof(a) == typeof(b), smuteg :(
}

func Deduplicate(data *[]Eq) { … } // Dalej nie możemy mieć pewności, że wszystkie elementy tablicy są takie same

No i tutaj pojawia się nagle zonk, otóż ktoś gdzieś (przez przypadek, niewiedzę, cokolwiek) implementuje funkcję:

type x struct {
    a float64
}

func (a  x) eq(b Eq) bool {
    return a.a == b.a
}

No i nasza funkcja przestaje działać prawidłowo.

Pokaż cały kod, i napisz co chcesz zrobić - rzuć do playground czy coś.

Wynika z tego, że albo musimy wykazywać odpowiednią kreatywność w wymyślaniu metod dla naszych struktur albo liczyć się z możliwymi problemami w przyszłości. Języki, w których metody są powiązane z interfejsem nie mają takich problemów, przykład:

Mógłbym użyć argumentu o linterach, ale nie jestem złośliwy :P

2

O gicie (go get) i go deps pewnie jednak nie słyszałeś? Czyli do tej pory, niby źle robiłem, że mi to działało? Przypominam: w gicie jest wersjonowanie.

To jak określisz jaką wersję ma pobrać Twój projekt? Git ma wersjonowanie, go get nie ma (nie miał?) oficjalnego wsparcia do tego i zawsze używał HEAD.

IMHO jest bardzo naukowym argumentem jak widzisz. Do tego nie do tego piłem, error checking w języku nie powinny rozwiązywać żadne zewnętrzne lintery! IMO error checking w Go, to co zarzuciłeś wyżej, jest OK. Wchodzę w byle jaki kod i od razu wiem co to robi bez orientowania się, w której sekcji jestem. Ja to lubię dla odmiany, co teraz? Kto ma rację?

Ale jakie lintery? O czym ty do mnie w ogóle?

Przykład na obsługę błędów bez linterów zewnętrznych, wyjątków czy nil:

match foo() {
  Ok(value) => bar(value),
  Err(error) => panic!("{:?}". error),
}

a jak nie lubisz tej składni to masz:

bar(foo()?)

lub

bar(try!(foo))

Nie musisz się zastanawiać "w której sekcji jesteś".

Nie ma generics - nie używasz, no proste. Twórcy języka tak chcieli i jest. […] Albo inaczej: stwórz własny język - patrz pierwsza odpowiedź.

Tytuł wątku to Go - wasze doświadczenia z tym językiem. więc opisuję swoje doświadczenia.

Ponieważ jednak coraz więcej softu powstaje w Go (https://awesome-go.com polecam), to jednak chyba te Twoje problemy nie są tak istotne, nie?

Dokładnie to samo można powiedzieć o PHP, a nie nazwał bym tego języka "wzorem do naśladowania" czy niedoścignionym ideałem.

I znowu dochodzimy do IMHO, twórcy tak uznali i tyle. Źle? To pisz w języku, gdzie masz funkcje min(), nie rozumiem zarzutu? Używając danego języka akceptujesz jego pradygmaty, proste. Jak Ci nie pasują - to go nie używasz. Jest też metoda pośrednia - używaj dobrego języka do danego zastosowania nawet mieszając je między sobą w obrębie jednego projektu, jeśli to możliwe. Bo każdemu językowi można zarzucić, że język A nie ma tego, a język B to ma (i odwrotnie)

Patrz wyżej (o temacie wątku).

Wiesz, jak mnie praca przeszkadza, to ją zmieniam, ale podobno ja dziwny jestem...

Nie praca mi przeszkadza, nawet język mi nie przeszkadza bo go lubię, ale to nie oznacza, że język nie ma irytujących wad.

Niestety Gophers tego nie potrafią zrozumieć, bo Go jest językiem idealnym, bo Commander Pike wynalazł kanały, przez co to jest jedyny język, w którym można łatwo pisać programy współbieżne. /s

Mógłbym użyć argumentu o linterach, ale nie jestem złośliwy :P

Sam fakt potrzeby istnienia linterów wynika z ułomności języków programowania. Im mniej linter musi sprawdzać, bo kompilator wyłapuje to jako błąd, tym lepiej.

Lintery to jest próba poprawy tego co w języku zostało skopane, bo skoro może istnieć statyczny analizer, który to robi, to dlaczego nie można było tego umieścić w języku od samego początku?

1
hauleth napisał(a):

O gicie (go get) i go deps pewnie jednak nie słyszałeś? Czyli do tej pory, niby źle robiłem, że mi to działało? Przypominam: w gicie jest wersjonowanie.

To jak określisz jaką wersję ma pobrać Twój projekt? Git ma wersjonowanie, go get nie ma (nie miał?) oficjalnego wsparcia do tego i zawsze używał HEAD.

"go get" + wchodze $GOPATH/src/<destination> i zrobić git checkout tags/version?
A jak chcę "utrwalić" zależności to go deps/tym podobne narzędzia

IMHO jest bardzo naukowym argumentem jak widzisz. Do tego nie do tego piłem, error checking w języku nie powinny rozwiązywać żadne zewnętrzne lintery! IMO error checking w Go, to co zarzuciłeś wyżej, jest OK. Wchodzę w byle jaki kod i od razu wiem co to robi bez orientowania się, w której sekcji jestem. Ja to lubię dla odmiany, co teraz? Kto ma rację?

Ale jakie lintery? O czym ty do mnie w ogóle?

Przykład na obsługę błędów bez linterów zewnętrznych, wyjątków czy nil:

match foo() {
  Ok(value) => bar(value),
  Err(error) => panic!("{:?}". error),
}

a jak nie lubisz tej składni to masz:

bar(foo()?)

lub

bar(try!(foo))

Nie musisz się zastanawiać "w której sekcji jesteś".

Sprawa wyszła z tego:

  1. Stwierdziłeś, że if err != nil (i podobne konstrukcje) zawsze występuja, bo nil/null istnieje w Go
  2. Dałem CI jeden z wielu argumentów, dlaczego nie uważam tego za coś złego (mam rację? nie mam?)
  3. Wyskoczyłeś, z linterami
  4. Teraz wyskoczyłeś z kodem z innego języka

Najlepiej pokaż case, czyli Twój code, że to nil/null i to całe error checking bez panikowania jest bez sensu.

  • ten case z interfaces też pokaż cały, a nie wycinek

Niestety Gophers tego nie potrafią zrozumieć, bo Go jest językiem idealnym, bo Commander Pike wynalazł kanały, przez co to jest jedyny język, w którym można łatwo pisać programy współbieżne. /s

Zaraz, którzy Gophers tak uważają? W sensie, że Go to język idealny, chętnie przeczytam. Nigdzie nie mogę na to trafić. Sam Pike twierdzi, że on wie, że język ma swoje problemy i nakreśla, na czym oni się skupiają w rozwoju. Poza tym, uparłeś się na tego Pike jakby Ci coś zrobił - tam ich jest kilku...

Mógłbym użyć argumentu o linterach, ale nie jestem złośliwy :P

Sam fakt potrzeby istnienia linterów wynika z ułomności języków programowania. Im mniej linter musi sprawdzać, bo kompilator wyłapuje to jako błąd, tym lepiej.

Lintery to jest próba poprawy tego co w języku zostało skopane, bo skoro może istnieć statyczny analizer, który to robi, to dlaczego nie można było tego umieścić w języku od samego początku?

Dlatego warto pamiętać, że Go jest silnie typowalne (dlatego odradzam interface{}) + masz go vet|fmt|tool w standardzie

1
  1. Stwierdziłeś, że if err != nil (i podobne konstrukcje) zawsze występuja, bo nil/null istnieje w Go

Samo istnienie nil jest błędem projektowym języka programowania. Taka wartość nigdy nie powinna mieć miejsca.

  1. Dałem CI jeden z wielu argumentów, dlaczego nie uważam tego za coś złego (mam rację? nie mam?)

Podając jako przykład obsługę błędów w Go, która jest wg mnie najgorszą rzeczą w tym języku. Chcesz mieć funkcję, która zwraca wartość lub błąd jeśli wystąpi?

val, err := foo()
if err != nil { … }

Que? Zwraca wartość lub błąd. Jak zwróci błąd, to wartość jest bezwartościowa, więc czemu ona w ogóle jest? Problem z obsługą błędów w Go jest taki, że bardzo łatwo jest je zignorować lub zapomnieć sprawdzić.

  1. Wyskoczyłeś, z linterami

Tylko ja nie mówiłem o error checking, tylko o pisaniu kodu na bazylion linii. Jak masz krótkie funkcje, to nie zdarzy Ci się, że przeoczysz obsługę błędów, bo nie masz bazyliona linii.

  1. Teraz wyskoczyłeś z kodem z innego języka

Jak mamy porównać obsługę błędów z Go vs inne możliwości to jak mam pokazywać kod z Go?

Najlepiej pokaż case, czyli Twój code, że to nil/null i to całe error checking bez panikowania jest bez sensu.

Zupełnie nie zrozumiałeś mojego przekazu. Oczywiście, że error checking bez panikowania ma sens. A apropos panic to to jest de facto wyjątek, tylko taki, którego nie da się złapać. nil checking jest zły, ale on nie ma nic wspólnego z moim kodem przedstawionym wyżej.

Ale po kolei, wpierw interfejsy.

Masz przykład z interfejsami, który nie jest poprawny, i uwierz mi, że ten program jest niepoprawny nawet jeśli wiemy, że zawsze będziemy używać tylko struktury x. Dodatkowo nie da się specjalnie napisać metody Deduplicate tak by mieć 100% pewności, że albo zadziała, albo się nie skompiluje, co możemy zrobić w Javie, C#, C++, Rust, Haskell, czy jakimkolwiek innym języku z porządnym statycznym typowaniem (nie, C nie ma statycznego typowania, przykład static int main[] = {}; się skompiluje i zlinkuje, ale może być problem z odpaleniem). Przedstawiony program może działać całkiem długo bez błędu, aż się zdarzy nieszczęśliwy wypadek i nie zadziała. A w przeciwieństwie do Erlanga nie ma tutaj wbudowanego rozwiązania pozwalającego na "wyratowanie" takich sytuacji.

Co do nil checking i bycia bez sensu, przykład:

val, err := fetchData()
if err != nil {
  sentry.SendErrror(err) // Cokolwiek, nie pamiętam jak to jest
}

val.foo = 10

Całkiem łatwo pociągnąć wykonanie dalej zapominając o return. W końcu mamy wartość dalej dostępną. Tylko ta wartość jest bez sensu.

Kolejny przykład:

func Foo(bar *Bar) {
  if bar == nil {
    fmt.Error("Unexpected nil")
  }

  // zrób coś… ale jak zapomnisz powyższego, to kompilator zrobi… nic
}

Musisz sprawdzać czy nie przekazano Ci przypadkiem nila lub będziesz miał null pointer exception, czy cokolwiek tam jest w Go (jak chcesz to mogę to nazwać null pointer panic, wychodzi na jedno). Czy nie łatwiej byłoby zapobiec takiej możliwości w całości? Np., no nie wiem, nie mając wartości nil? Przykład w Ruscie:

fn foo(bar: &Bar) {
  // bar na 100% jest możliwe do użycia
}

// jak chcesz jednak obsłużyć możliwość podania niczego

fn faa(bar: Option<&Bar>) {
  let bar = match bar {
    Some(val) => val,
    None => return
  };

  // Na 100% nie ominiesz powyższego, bo inaczej będziesz miał w czasie kompilacji niezgodność typów

  // A jeśli chcesz domyślną wartość w przypadku `None`, to wystarczy:
  // let bar = bar.unwrap_or(default);
}

Nawet C++, Java i C# się ostatnio dorabiają typów Option, które pozwalają na ograniczenie miejsc, gdzie musisz mieć null checki.

2
hauleth napisał(a):
  1. Stwierdziłeś, że if err != nil (i podobne konstrukcje) zawsze występuja, bo nil/null istnieje w Go

Samo istnienie nil jest błędem projektowym języka programowania. Taka wartość nigdy nie powinna mieć miejsca.

Ale z czego ta prawda wynika? Z jakiego założenia?

  1. Dałem CI jeden z wielu argumentów, dlaczego nie uważam tego za coś złego (mam rację? nie mam?)

Podając jako przykład obsługę błędów w Go, która jest wg mnie najgorszą rzeczą w tym języku. Chcesz mieć funkcję, która zwraca wartość lub błąd jeśli wystąpi?

val, err := foo()
if err != nil { … }

Que? Zwraca wartość lub błąd. Jak zwróci błąd, to wartość jest bezwartościowa, więc czemu ona w ogóle jest? Problem z obsługą błędów w Go jest taki, że bardzo łatwo jest je zignorować lub zapomnieć sprawdzić.

Dostajesz dwie wartości. A jak Ci funkcja zwraca dwie (wartosc i error) jak większość w stdlib, to jeśli "zapomnisz" o drugiej wartości - masz błąd kompilacji (two values return in single context). Jak ją "pobierzesz", lecz olejesz - masz błąd kompilacji (declared but not used). Jak "siłowo" olejesz (_) to wtedy już sam sobie jesteś winny. Jak widzisz, kompilator stoi tu nad Tobą z batem i trzeba wyjątkowo dołożyć starań, aby "zapomnieć sprawdzić".

  1. Wyskoczyłeś, z linterami

Tylko ja nie mówiłem o error checking, tylko o pisaniu kodu na bazylion linii. Jak masz krótkie funkcje, to nie zdarzy Ci się, że przeoczysz obsługę błędów, bo nie masz bazyliona linii.

W Go nie potrzebuję do tego lintera, bo konstrukcja if err != nil jest zazwyczaj maksymalnie krótka i zwięzła, do tego mówi mi, że sprawdza sytuacje z linijki przed nią - zawsze. A w takiej Javie (tylko przykład z życia)? Ctrl+F, szukam czegoś w kodzie, znajduje - ok, analiza flow góra/dół (ekran 3K, kod ponoć dobrej jakości) i nagle okazuję się, że ja jestem w dopiero obsłudze wyjątku... acha, no fajnie. No tak, mogłem użyć InteliJ pewnie... tylko po to, by coś sprawdzić w kodzie na szybko?

  1. Teraz wyskoczyłeś z kodem z innego języka

Jak mamy porównać obsługę błędów z Go vs inne możliwości to jak mam pokazywać kod z Go?

Najlepiej pokaż case, czyli Twój code, że to nil/null i to całe error checking bez panikowania jest bez sensu.

Zupełnie nie zrozumiałeś mojego przekazu. Oczywiście, że error checking bez panikowania ma sens.

A apropos panic to to jest de facto wyjątek, tylko taki, którego nie da się złapać. nil checking jest zły, ale on nie ma nic wspólnego z moim kodem przedstawionym wyżej

W Go panic powinien wystąpić, jeżeli zachodzi sytuacja, iż aplikacja w ogóle nie powinna działać (w ostateczności), więc to nie jest wyjątek (choć można go łapać - patrz: recover). Tako rzecze idiomatic Go.

Ale po kolei, wpierw interfejsy.

Dzięki za kod, rzucę na niego okiem jak będę miał chwilę - bo ztcw to jest on bez sensu zaprojektowany na pierwszy rzut oka.
Celem klaryfikacji: Tobie rozchodzi się, że teraz ktoś dopisze poniżej kolejną eq(), która będzie z automatu implementowała ów interfejs to się to posypie teraz/kiedyś, tak?

Co do nil checking i bycia bez sensu, przykład:

val, err := fetchData()
if err != nil {
  sentry.SendErrror(err) // Cokolwiek, nie pamiętam jak to jest
}

val.foo = 10

Całkiem łatwo pociągnąć wykonanie dalej zapominając o return. W końcu mamy wartość dalej dostępną. Tylko ta wartość jest bez sensu.

Takie rzeczy powinien Ci wyłapać vet, do tego właśnie służy, gdy już oszukałeś kompilator na maksa.

Kolejny przykład:

func Foo(bar *Bar) {
  if bar == nil {
    fmt.Error("Unexpected nil")
  }

  // zrób coś… ale jak zapomnisz powyższego, to kompilator zrobi… nic
}

Musisz sprawdzać czy nie przekazano Ci przypadkiem nila lub będziesz miał null pointer exception, czy cokolwiek tam jest w Go (jak chcesz to mogę to nazwać null pointer panic, wychodzi na jedno). Czy nie łatwiej byłoby zapobiec takiej możliwości w całości? Np., no nie wiem, nie mając wartości nil?

Zaraz, zaraz - dlaczego akurat tak dziwnie? To sytuacja ewidentnie dla kompilatora, a mnie ona przychodzi na myśl, kiedy sobie na siłę zrobię, nie wiem: var bar struct{} i z tego mam rzeczywiście nil pointera jako tzw. zero value. W sytuacji praktycznej, jak mam do takiej funkcji wsadzić nila? Chyba tylko celowo... bo tu też wygląda na gimnastykę z kompilatorem w grę "kto tu kogo przechytrzy". Oczywiście można, tylko po co?

0

Samo istnienie nil jest błędem projektowym języka programowania. Taka wartość nigdy nie powinna mieć miejsca.

Ale z czego ta prawda wynika? Z jakiego założenia?

Jaką wartość ma nil oprócz generowania błędów? Wyżej podałem cytat twórcy NULLa, który twierdzi, że gdyby wiedział ile to sprawi problemów, to nigdy by tego nie zrobił. Główną ideą języków programowania powinno być "make hard things easy and bad things impossible". nil jak najbardziej zalicza się do "bad things".

val, err := foo()
if err != nil { … }

Dostajesz dwie wartości. A jak Ci funkcja zwraca dwie (wartosc i error) jak większość w stdlib, to jeśli "zapomnisz" o drugiej wartości - masz błąd kompilacji (two values return in single context). Jak ją "pobierzesz", lecz olejesz - masz błąd kompilacji (declared but not used). Jak "siłowo" olejesz (_) to wtedy już sam sobie jesteś winny. Jak widzisz, kompilator stoi tu nad Tobą z batem i trzeba wyjątkowo dołożyć starań, aby "zapomnieć sprawdzić".

Nie, stałby z batem, gdyby mi w ogóle nie pozwolił użyć val jeśli wystąpił błąd. Do tego właśnie są sum types (aka nazwane unie).

W Go nie potrzebuję do tego lintera, bo konstrukcja if err != nil jest zazwyczaj maksymalnie krótka i zwięzła, do tego mówi mi, że sprawdza sytuacje z linijki przed nią - zawsze. A w takiej Javie (tylko przykład z życia)? Ctrl+F, szukam czegoś w kodzie, znajduje - ok, analiza flow góra/dół (ekran 3K, kod ponoć dobrej jakości) i nagle okazuję się, że ja jestem w dopiero obsłudze wyjątku... acha, no fajnie. No tak, mogłem użyć InteliJ pewnie... tylko po to, by coś sprawdzić w kodzie na szybko?

Po kolei, bo widzę, że zakręciłeś się i to mocno.

  • Pisanie kodu na wiele linii to jest problem niezależny od języka.
  • Co stoi na przeszkodzie by mieć obsługę błędów na miejscu? Niezależnie od języka. Przykładowo niech będzie Java
String fileContent;

try {
  fileContent = readFile();
} catch (/* wyjątek */) {
  throw new CannotReadFileException();
}

// Reszta kodu

To wszystko zależy od programisty, nie od języka. Ale jak język ma idiotyczną obsługę błędów, to nie masz specjalnie wyboru i musisz wykonywać idiotyczne akcje.

W Go panic powinien wystąpić, jeżeli zachodzi sytuacja, iż aplikacja w ogóle nie powinna działać (w ostateczności), więc to nie jest wyjątek (choć można go łapać - patrz: recover). Tako rzecze idiomatic Go.

Czyli tak samo jak wyjątki w każdym mi znanym języku programowania.

Ale po kolei, wpierw interfejsy.

Dzięki za kod, rzucę na niego okiem jak będę miał chwilę - bo ztcw to jest on bez sensu zaprojektowany na pierwszy rzut oka.
Celem klaryfikacji: Tobie rozchodzi się, że teraz ktoś dopisze poniżej kolejną eq(), która będzie z automatu implementowała ów interfejs to się to posypie teraz/kiedyś, tak?

Nie tylko. Nawet w przedstawionym przeze mnie kodzie jest błąd, ale ciekawi mnie ile czasu zamie Ci jego znalezienie.

Takie rzeczy powinien Ci wyłapać vet, do tego właśnie służy, gdy już oszukałeś kompilator na maksa.

A co jeśli w przypadku błędu chciałem kontynuować wykonywanie ale z domyślną wartością? Lub rzeczywiście olać błąd?

Przykład, który to obrazuje i dla którego go vet nie zwraca żadnych problemów.

Zaraz, zaraz - dlaczego akurat tak dziwnie? To sytuacja ewidentnie dla kompilatora, a mnie ona przychodzi na myśl, kiedy sobie na siłę zrobię, nie wiem: var bar struct{} i z tego mam rzeczywiście nil pointera jako tzw. zero value. W sytuacji praktycznej, jak mam do takiej funkcji wsadzić nila? Chyba tylko celowo... bo tu też wygląda na gimnastykę z kompilatorem w grę "kto tu kogo przechytrzy". Oczywiście można, tylko po co?

"Nic dziwnego, że odstrzeliło Ci stopę, na 9437 stronie dokumentacji masz dokładnie napisane, że funkcja XYZ nie może przyjąć nil, chyba, że masz ustawioną flagę ABC lub zmienną środowiskową DEF", przepraszam, ale takie cyrki to PHP. Myślałem, że skoro tworzymy nowy język w XXI wieku, to staramy się uniknąć błędów poprzedników, a nie szczęśliwie je powielać a potem uzasadniać, że "jak się dobrze tego używa to nie ma problemu". Jeśli jakiś feature nie ma być używany, to najlepszym wyjściem jest to by nigdy on nie powstał.

Co do tego, że "nigdy tam nil nie wyląduje chyba, że celowo" to połącz sobie to z powyższym problemem i masz już nieszczęście gotowe.

2

Ja tam nil (nie mylić z NULL) lubię.
Ale nie każdy łapie jak tego używać, przykład z dzisiaj: http://tinypic.com/r/osvprd/9

0

https://blog.golang.org/8years
Niedawno popelnilem troche kodu w pracy.
Ogolnie to lubie Go.

0

No i tutaj pojawia się nagle zonk, otóż ktoś gdzieś (przez przypadek, niewiedzę, cokolwiek) implementuje funkcję:

type x struct {
a float64
}

func (a x) eq(b Eq) bool {
return a.a == b.a
}
No i nasza funkcja przestaje działać prawidłowo.

Modzie ogarnij wodze fantazji. W każdym języku można napisać kod, który pobruździ w innym miejscu. W każdym. Go nie jest żadnym wyjątkiem. Szczególnie fikuśnie można to zrobić w językach pozwalających na modyfikację typów i metod wbudowanych. Choć zgadzam się z tym, że null to nie najszczęśliwszy koncept, to jednak bądźmy poważni.

0
Mistrzowski Kura napisał(a):

Modzie ogarnij wodze fantazji. W każdym języku można napisać kod, który pobruździ w innym miejscu. W każdym.

Ja nie mówię, że wszystkie języki poza Go uniemożliwiają takie rzeczy. Tu chodzi o to jak łatwo można popełnić taki błąd lub nie. Przykładowo w takim Ruscie można też popełnić taki błąd, ale jest już trudniej, bo typy zmiennoprzecinkowe nie implementują Eq a tylko PartialEq, przez co taka deklaracja nie wystarczy i będzie błędem kompilatora:

#[derive(PartialEq, Eq)]
struct Foo {
    a: f64
}

W takim Elixirze też jest to dość trudne by "dodać" funkcjonalność, która Ci coś zepsuje. Oczywiście, w obu językach na pewno się da znaleźć jakiś patent by coś zepsuć, ale mam wrażenie, że w Go jest to szczególnie łatwe.

0

Mi Go nie przypadł do gustu, miał być taki prosty łatwiejszy od Javy do nauki, a jest dość trudny i dziwny. Wątpię czy zastąpi C, to już prędzej Rust.

0
Brunatny Wąż napisał(a):

Mi Go nie przypadł do gustu, miał być taki prosty łatwiejszy od Javy do nauki, a jest dość trudny i dziwny. Wątpię czy zastąpi C, to już prędzej Rust.

Go jak już to zastąpi Pythona w aplikacjach i skryptach administracyjnych, bo mniejszy i łatwiejszy w deploymencie.

0

Moze nie dotyczy bezposrednio samego Go tylko Iris MVC (back-end Go framework) ale ciekawy test: https://visualstudiomagazine.com/articles/2017/08/22/net-core-benchmarking.aspx?m=1

0
hauleth napisał(a):
Mistrzowski Kura napisał(a):

Modzie ogarnij wodze fantazji. W każdym języku można napisać kod, który pobruździ w innym miejscu. W każdym.

Ja nie mówię, że wszystkie języki poza Go uniemożliwiają takie rzeczy. Tu chodzi o to jak łatwo można popełnić taki błąd lub nie.

Witam, sorry, że nie odzywałem, raz, że trochę zapomniałem o wątku, dwa, chciałem się Twojemu przykładowi przyjrzeć, a jego kod tego nie ułatwiał. Ale możemy kontynuować. Odnośnie Twojego przykładu - dla mnie błędem kompilatora jest, że Ci pozwala w funkcji interfejsowej podać typ interfejsu, takiego galimatiasu ja bym zabronił na etapie kompilacji bo mnie to wygląda na bad practise (grepnąłem właśnie źródła Go i tam nie widzę takiego "zagrania")

Ale idąc dalej, użyłem Twojego kodu i spłodziłem moją próbę "podebrania" metody, o tak:

type y struct {
	a int64
}

func (a y) eq(b Eq) bool {
	other := b.(x)

	return a.a == other.a
}

Po prostu to dopisałem do Twojego kodu i o to wynik: https://play.golang.org/p/UhqLF3gpzFS

Czyli nie tak łatwo jak piszesz, tu spróbowałem z int64 bo to mógłby być częsty scenariusz, że komuś omyłkowo kompilator "wciągnie" inny typ, ale nie wciągnie.

Z kolei gdy spróbuję, powiedzmy znowu z float64 (nigdy coś takiego by code review nie przeszło, bo to masło maślane) np:

type y struct {
	a float64
}

func (a y) eq(b Eq) bool {
    // Drobna "modyfikacja", która celowo psuje działanie
	return false
}

type x struct {
	a float64
}

func (a x) eq(b Eq) bool {
	other := b.(x)

	return a.a == other.a
}

Tutaj wówczas użyta jest druga funkcja, ta teoretycznie właściwa, obojętnie gdzie umieszczę ponieważ moja "celowo popsuta" nie ma typu x - gdy skasuję Twoją wówczas jest błąd kompilacji. Tak samo gdy odwrócę warunek z Twojego Eq()

Tym sposobem, ja nie neguję, że kompilatora nie da się oszukać, bo się da. Pytanie pozostaje: po co? W każdym języku można coś takiego wymodzić, a tutaj gimnastyka jest niezła dla pokazania, że się da, ale praktyczność z tego jest mała.

Teraz jeszcze kwestia wyjaśnienia nil dla Ciebie, bo strasznie Cie on kole. Otóż nil w Go to nie jest nullptr/null z innych języków. W języku Go to jest tzw: zero value (dla kanałów, interfejsów, struktur) i jest to wartość używalna (np. w kanałach). To nie jest "nic" w sensu rozumienia "nic" lub "nie wiadomo co to". Zostało to wprowadzone, aby każda zmienna była defaultowo (dla której jest on domyślnym zero value) zainicjowana i nie występowały niezdefiniowane zachowania czy inne takie. I do tego jest to wartość całkowicie użyteczna, to dokładniejsze wyjaśnienie:

Oczywiście, możesz mieć inne zapatrywanie na to, czy to jest dobre, czy złe, czy jakie, to będzie Twoja opinia. Natomiast nie myl nila z klasykami z C/C++

0

@hauleth: co do Twojego jednego zarzutu odnośnie wersjonowania, to obecnie są:

  • dep - de facto standard od jakiegoś czasu (algorytmy podobne do tych z Cargo w Rust)
  • vgo, to nowość od core team Go, jako niby "lepszy" dep, ale algorytmu jest nieco bardziej zakręcony niżby się można było spodziewać

Generalnie jest teraz o to mały shitstorm, bo dep wyewoluował jako produkt społeczności, a vgo przeszedł "z automatu" (przynajmniej tak to z boku wyglądało) jako propozycja pracowników Google (tych od Go). Według mnie, finalnie nic nie będzie przeszkadzać w istnieniu tych dwóch tworów jednocześnie i każdy sobie będzie wybierać to, co mu wygodnie, bo raczej w podstawowych sprawach będą ze sobą w miarę zgodne.

1 użytkowników online, w tym zalogowanych: 0, gości: 1